/*
This work was originally done by Rob Hackett (R.Hackett@bom.gov.au) at
the Australian Bureau of Meteorology in the au.gov.bom.aifs.osa.charts
package. Jeff McWhirter (jeffmc@unidata.ucar.edu) refactored it to
remove dependencies on the charts package and have it be a stand-alone
scene graph renderer for visad Displays
Copyright (C) 2017 Australian Bureau of Meteorology.
This library is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This library is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this library; if not, write to the Free Software Foundation, Inc.,
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package visad.bom;
import visad.*;
import visad.java2d.DisplayImplJ2D;
import visad.java2d.DisplayRendererJ2D;
import visad.java3d.DisplayImplJ3D;
import visad.java3d.DisplayRendererJ3D;
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import java.awt.print.PageFormat;
import java.awt.print.Printable;
import java.rmi.RemoteException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import javax.media.j3d.*;
import javax.swing.*;
import javax.vecmath.Color3f;
/**
* Render the non-texture components of a scene graph to a Graphics2D.
* This does not handle any 3D aspect, rotation, etc.
*/
public class SceneGraphRenderer {
/** default width */
private final int DEFAULT_WIDTH = 640;
/** default height */
private final int DEFAULT_HEIGHT = 512;
/** actual width */
protected int width = DEFAULT_WIDTH;
/** actual height */
protected int height = DEFAULT_HEIGHT;
/** 2D mode */
public static final int MODE_2D = 2;
/** 3D mode */
public static final int MODE_3D = 3;
// Default to 3d mode
/** actual mode */
private int mode = MODE_3D;
/** pixel width */
protected float pixelWidth = 1.0f;
/** page color */
private Color pageColour;
/** frame color */
private Color frameColour = Color.black;
/** Hatching */
Hatching hatching = new Hatching();
/** flag for monochrome */
protected boolean monochrome;
/** flag for gradient fill */
protected boolean gradientFill = false;
/** flag for transparency */
protected boolean useTransparency = true;
/** list of colours */
protected ArrayList colours = new ArrayList();
/** the line path */
private GeneralPath linePath;
/** plot map flag */
protected boolean plotMap = true;
/** the viewport */
protected AffineTransform viewPort;
/** display side coordinate system */
CoordinateSystem coordSys;
/** Mouse behavior */
MouseBehavior behavior;
/** line thickness */
private double lineThickness = 1;
/** transform to screen */
private boolean transformToScreenCoords = false;
/**
* Default constructor
*/
public SceneGraphRenderer() {}
/**
* Get the display side CoordinateSystem
*
* @return the display side CoordinateSystem or null
*/
private CoordinateSystem getCoordinateSystem() {
return coordSys;
}
/**
* Get the viewport
*
* @param displayImpl the display
* @return the viewport
*/
private AffineTransform getViewport(DisplayImpl displayImpl) {
ProjectionControl pc = displayImpl.getProjectionControl();
behavior = displayImpl.getDisplayRenderer().getMouseBehavior();
double[] tstart = pc.getMatrix();
double[] rotArray = new double[3];
double[] scaleArray = new double[3];
double[] transArray = new double[3];
behavior.instance_unmake_matrix(rotArray, scaleArray, transArray, tstart);
float lScaleX = (float)scaleArray[0];
float lScaleY = (float)scaleArray[1];
float lTransX = (float)transArray[0];
float lTransY = (float)transArray[1];
// Get the panning/scanning of the location
// Convert the 3D viewport from the Location, into a 2D
// Affine transform.
// get translation, then append scale NOT other way round
viewPort = AffineTransform.getTranslateInstance(lTransX, lTransY);
AffineTransform scale = AffineTransform.getScaleInstance(lScaleX,
lScaleY);
viewPort.concatenate(scale);
AffineTransform rot =
AffineTransform.getRotateInstance(Math.toRadians(-rotArray[2]));
viewPort.concatenate(rot);
// Create another transform which matches the viewport
// coordinates to the device coordinates
float deviceScaleX = (float)width / 2.0f;
float deviceTransX = width / 2.0f;
float deviceTransY = height / 2.0f;
AffineTransform deviceTrans =
AffineTransform.getTranslateInstance(deviceTransX, deviceTransY);
AffineTransform deviceScale =
AffineTransform.getScaleInstance(deviceScaleX, -deviceScaleX);
deviceTrans.concatenate(deviceScale);
// Join the two together so that coordinates in viewport space
// line up with the output coordinates
viewPort.preConcatenate(deviceTrans);
return viewPort;
}
/**
* Return an array of Longitudes/Latitudes corresponding to the
* pixels of the chart. Based on the Charts CoordinateSystem and
* ViewPort.
* In the case of an A0 chart, the number of pixels may be
* huge, so supply an X and Y size allowing a smaller subset to be used
* if necessary
* The purpose of this method is to allow an image to be
* interpolated onto the chart as an overlay (eg. sat image)
* @param xSize The size of the target X dimension
* @param ySize The size of the target Y dimension
* @return An array of Longitudes/Latitudes corresponding to the
* pixels of the chart, sampled to match the target x/y size.
* Based on the Charts CoordinateSystem and ViewPort
*/
public float[][] getLonLatSamples(int xSize, int ySize) {
float xScale = (float)width / (float)xSize;
float yScale = (float)height / (float)ySize;
CoordinateSystem coordSys = getCoordinateSystem();
float[][] lonLatSamples = null;
try {
AffineTransform inverse = viewPort.createInverse();
// Get the locations of each pixel of the chart
float[] pixelSamples = getPixelSamples(xScale, yScale);
// Normalise these using the inverse of the ViewPort
float[] normSamples = new float[xSize * ySize * 2];
inverse.transform(pixelSamples, 0, normSamples, 0, xSize * ySize);
// Convert normal coordinates to lons/lats, using the
// chart coordinate system
float[][] xys = new float[3][normSamples.length / 2];
for (int i = 0; i < normSamples.length / 2; i++) {
xys[0][i] = normSamples[i * 2];
xys[1][i] = normSamples[i * 2 + 1];
xys[2][i] = 0.0f;
}
float[][] lonLats3d = coordSys.fromReference(xys);
// Finally, filter out the extraneous 3rd dimension
float[][] lonLats2D = {
lonLats3d[1], lonLats3d[0]
};
lonLatSamples = lonLats2D;
}
catch (NoninvertibleTransformException e) {
System.err.println("Chart.getLonLatSamples: " + e);
}
catch (VisADException e) {
System.err.println("Chart.getLonLatSamples: " + e);
}
return lonLatSamples;
}
/**
* Get the x/y coordinate of every pixel in the chart
*
* @param xScale the x scale
* @param yScale the y scale
* @return
*/
private float[] getPixelSamples(float xScale, float yScale) {
int xSize = (int)((float)width / xScale);
int ySize = (int)((float)height / yScale);
float[] samples = new float[xSize * ySize * 2];
int cnt = 0;
for (int i = 0; i < xSize; i++) {
for (int j = 0; j < ySize; j++) {
samples[cnt] = i * xScale;
samples[cnt + 1] = j * yScale;
cnt += 2;
}
}
return samples;
}
/**
* Get a list of the colours used in the chart
* This may be necessary if the chart is being plotted to a medium
* with limited colours, eg. 8 bit PNG. The antialiasing and gradient
* fill used in some charts can easily grab all the available colours
* By taking the ones used specifically by the chart, the important
* colours can be reserved in the colour table ensuring that any
* colour loss has only minimal cosmetic effect
* @return The colours used by this chart
*/
public Color[] getColours() {
Color[] colourArr = new Color[colours.size()];
colours.toArray(colourArr);
return colourArr;
}
/**
* Get the line width
*
* @return the line width
*/
public float getLineWidth() {
return (float)lineThickness;
}
/**
* Create a gradient fill. If this is set with graphics.setPaint()
* then any shapes which are filled will have a slight gradient
* from darker to lighter along the line specified by x1, y1 to x2, y2
* @param colour The colour of the middle point of the gradient
* @param x1 The X coordinate of the point where the darkest part of
* the gradient will be
* @param y1 The Y coordinate of the point where the darkest part of
* the gradient will be
* @param x2 The X coordinate of the point where the lightest part of
* the gradient will be
* @param y2 The Y coordinate of the point where the lightest part of
* the gradient will be
* @return
*/
private GradientPaint makeGradient(Color colour, float x1, float y1,
float x2, float y2) {
float[] rgb = new float[4];
colour.getRGBColorComponents(rgb);
// Define the difference between the lightest and darkest
// colours
float diff = 0.05f;
float[] darkRGB = new float[4];
darkRGB[0] = rgb[0] * (1.0f - diff);
darkRGB[1] = rgb[1] * (1.0f - diff);
darkRGB[2] = rgb[2] * (1.0f - diff);
darkRGB[3] = rgb[3];
float[] lightRGB = new float[4];
lightRGB[0] = rgb[0] * (1.0f + diff);
lightRGB[1] = rgb[1] * (1.0f + diff);
lightRGB[2] = rgb[2] * (1.0f + diff);
lightRGB[3] = rgb[3];
for (int i = 0; i < 4; i++) {
if (lightRGB[i] > 1.0) {
lightRGB[i] = 1.0f;
}
}
Color dark = new Color(darkRGB[0], darkRGB[1], darkRGB[2]);
Color light = new Color(lightRGB[0], lightRGB[1], lightRGB[2]);
GradientPaint gradient = new GradientPaint(x1, y1, dark, x2, y2, light);
return gradient;
}
/**
* Render the display to the graphics
*
* @param graphics the graphics object
* @param display the display to render
*/
private void render(Graphics2D graphics, DisplayImpl display) {
copyVisadDisplay(display, graphics);
}
/**
* Implements the Plottable interface. This will plot the chart
* to a vector graphics file
* Don't use this method directly. It is called by the Plotter class
*
* @param graphics The java2d object used by the third party graphics
* library to render the graphics file
* @param display the display to render
* @param cs the display side coordinate system
* @param width the width of the output
* @param height the height of the output
*/
public void plot(Graphics2D graphics, DisplayImpl display,
CoordinateSystem cs, int width, int height) {
this.width = width;
this.height = height;
coordSys = cs;
viewPort = getViewport(display);
/*jeffmc: We don't do this for now
// If a pre rendered background has been supplied
if ( !plotMap) {
// Add this to the start of the SVG
graphics.setColor(Color.BLACK);
// graphics.setColor(Color.WHITE);
graphics.fillRect(0, 0, width, height);
} else {
// Otherwise
if (gradientFill) {
GradientPaint oceanGradient = makeGradient(pageColour, width,
height, 0, 0);
graphics.setPaint(oceanGradient);
} else {
graphics.setColor(pageColour);
}
graphics.fillRect(0, 0, width, height);
}*/
int lineThick = (int)lineThickness;
graphics.setClip(0, 0, width, height);
// Render the chart
render(graphics, display);
// Draw a frame around the page
/*
drawEdge((Graphics2D) graphics, width, height,
(float) lineThickness * 2);
((Graphics2D) graphics).setColor(frameColour);
((Graphics2D) graphics).setStroke(getStroke(lineThick));
*/
colours.add(frameColour);
}
/**
* Print the display
*
* @param graphics Graphics to print to
* @param pageFormat the page format
* @param pageIndex the page index
* @param display the display to render
* @param cs the display side coordinate system
*
* @return flag for success
*/
public int print(Graphics graphics, PageFormat pageFormat, int pageIndex,
DisplayImpl display, CoordinateSystem cs) {
coordSys = cs;
viewPort = getViewport(display);
// Charts are ALL only 1 page
// You MUST do this, otherwise it prints infinite copies
// which can be a bad thing
if (pageIndex > 0) {
return Printable.NO_SUCH_PAGE;
}
// DO NOT use trasparency if chart is to be printed
// It causes the image to be rasterized which takes
// up lots of memory
useTransparency = false;
// Assuming a 2d graphics ...
if (graphics instanceof Graphics2D) {
Graphics2D graphics2D = (Graphics2D)graphics;
// Scale the chart to the page format
float scale = scaleToPage(pageFormat, graphics2D, width, height);
// Reduce the line thinkness to take advantage of the extra
// resolution of the printer
lineThickness = 1.0 / scale;
graphics2D.setColor(pageColour);
if (plotMap) {
graphics2D.fillRect(0, 0, width, height);
}
graphics2D.setClip(0, 0, width, height);
// Render the chart
render(graphics2D, display);
boolean transparent = !monochrome && useTransparency;
float legendScale = (float)lineThickness * 4;
drawEdge(graphics2D, width, height, (float)lineThickness * 4);
colours.add(frameColour);
}
else {
System.err.println("Wrong graphics type!" + " How did THAT happen?");
}
return Printable.PAGE_EXISTS;
}
/**
* Draw a frame around the page
* @param graphics
* @param width
* @param height
* @param thickness
*/
private void drawEdge(Graphics2D graphics, int width, int height,
float thickness) {
graphics.setColor(frameColour);
graphics.setStroke(getStroke(thickness));
float top = 0;
float bottom = (float)height;
float left = 0;
float right = (float)width;
// Cant use draw rect because we need float parameters
GeneralPath linePath = new GeneralPath();
linePath.moveTo(left, top);
linePath.lineTo(right, top);
linePath.lineTo(right, bottom);
linePath.lineTo(left, bottom);
linePath.lineTo(left, top);
graphics.draw(linePath);
}
/**
* Scale the chart so that it will fit onto the page
* @param pageFormat The specifications of the page
* @param graphics The graphics context
* @param xSize The width of the chart
* @param ySize The height of the chart
* @return The amount that the chart has to be rescaled to match the
* resolution of the printer
*/
private float scaleToPage(PageFormat pageFormat, Graphics2D graphics,
int xSize, int ySize) {
// Assume printers have a DPI of 300
// TODO Can this be inferred from the PrintService?
final int DPI = 300;
// Find out the size of the imageable part of the paper
// (in units of 1/72 inches)
double pageWidth72 = pageFormat.getImageableWidth();
double pageHeight72 = pageFormat.getImageableHeight();
// Scale the chart to fit this
double scaleX = pageWidth72 / xSize;
double scaleY = pageHeight72 / ySize;
// Choose the smallest of the 2 dimension scales
// Ensures that both dimensions will fit
double scale = scaleX;
if (scaleY < scaleX) {
scale = scaleY;
}
// Translate past the non-imageable bit
double transX = pageFormat.getImageableX() / scale;
double transY = pageFormat.getImageableY() / scale;
// Stretch the chart to match the dimensions of the paper
graphics.scale(scale, scale);
graphics.translate(transX, transY);
// Calculate how small a pixel is relative to the basic
// unit of size
// There are 300 pixels per inch
// One unit of size is 1/72 inch
// Dots per unit tells us how much to shrink the line width down
// to make it use a single dot
float dotsPerUnit = (DPI / 72);
return dotsPerUnit * (float)scale;
}
/**
* Draw a reprojected shape
*
* @param vertices vertices
* @param colour default color
* @param width line width
* @param graphics the graphics
*/
public void drawShapeReprojected(float[][] vertices, Color colour,
float width, Graphics2D graphics) {
drawShapeReprojected(vertices, colour, width, 0, graphics);
}
/**
* Draw a reprojected shape
*
* @param vertices vertices
* @param colour default color
* @param width line width
* @param dashStyle line dash style
* @param graphics the graphics
*/
public void drawShapeReprojected(float[][] vertices, Color colour,
float width, int dashStyle,
Graphics2D graphics) {
drawShapeReprojected(vertices, colour, width, dashStyle, graphics, false);
}
/**
* Draw a reprojected shape
*
* @param vertices vertices
* @param colour default color
* @param width line width
* @param dashStyle line dash style
* @param graphics the graphics
* @param isScreen vertices are in screen coordinates
*/
public void drawShapeReprojected(float[][] vertices, Color colour,
float width, int dashStyle,
Graphics2D graphics, boolean isScreen) {
graphics.setColor(colour);
/*
if (dashed) {
float[] dash = { 2.0f * pixelWidth, 6.0f * pixelWidth };
graphics.setStroke(getStroke(width, dash));
} else {
graphics.setStroke(getStroke(width));
}
*/
graphics.setStroke(
getStroke(width, getStrokeDash(dashStyle, pixelWidth)));
GeneralPath line = new GeneralPath();
line.moveTo(vertices[0][0], vertices[1][0]);
for (int j = 1; j < vertices[0].length; j++) {
line.lineTo(vertices[0][j], vertices[1][j]);
}
if (!isScreen) line.transform(viewPort);
line = clip(line);
graphics.setColor(colour);
//TODO: the colour have a 0 alpha
/*
graphics.setColor(
new Color(colour.getRed(), colour.getGreen(), colour.getBlue()));
*/
graphics.draw(line);
}
/**
* Draw the outline of a shape onto the chart
* @param vertices The vertices of the shape. A 2-Dimensional array.
* The first dimension contains the longitude values of the shape
* The second dimension contains the latitude values of the shape
* @param colour The colour of the shape
* @param width The thickness of the outline
* @param dashStyle Set to 0 if the outline is to be drawn as dashes
* @param graphics The java2d graphics object supplied by the
* plotting/printing medium
* @throws VisADException
*/
public void drawShape(float[][] vertices, Color colour, float width,
int dashStyle, Graphics2D graphics)
throws VisADException {
// Get the Coordinate System of the current location
CoordinateSystem coordSys = getCoordinateSystem();
// Filter out shapes which straddle the discontinuity of
// a mercator projection.
boolean crosses = crossesDiscontinuity(coordSys, vertices);
if (!crosses) {
// Reproject the coordinates to the native coordinate system
// eg. Longitude, Latitude -> X, Y
float[][] reprojected = coordSys.toReference(vertices);
drawShapeReprojected(reprojected, colour, width, dashStyle, graphics);
}
}
/**
* Draw the outline of a shape onto the chart
* @param vertices The vertices of the shape. A 2-Dimensional array.
* The first dimension contains the longitude values of the shape
* The second dimension contains the latitude values of the shape
* @param colour The colour of the shape
* @param width The thickness of the outline
* @param graphics The java2d graphics object supplied by the
* plotting/printing medium
* @throws VisADException
*/
public void drawShape(float[][] vertices, Color colour, float width,
Graphics2D graphics)
throws VisADException {
drawShape(vertices, colour, width, 0, graphics);
}
/**
* Fill a reprojected shape
*
* @param vertices the vertices of the shape
* @param colour the color
* @param graphics the graphics
*/
public void fillShapeReprojected(float[][] vertices, Color colour,
Graphics2D graphics) {
fillShapeReprojected(vertices, colour, graphics, false);
}
/**
* Fill a reprojected shape
*
* @param vertices the vertices of the shape
* @param colour the color
* @param graphics the graphics
* @param isScreen true if vertices are in screen (AWT) coordinates
*/
public void fillShapeReprojected(float[][] vertices, Color colour,
Graphics2D graphics, boolean isScreen) {
if (gradientFill) {
GradientPaint gradient = makeGradient(colour, 0, 0, width, height);
graphics.setPaint(gradient);
}
else {
graphics.setColor(colour);
}
GeneralPath shape = new GeneralPath();
if (vertices[0].length > 0) {
shape.moveTo(vertices[0][0], vertices[1][0]);
for (int j = 1; j < vertices[0].length; j++) {
shape.lineTo(vertices[0][j], vertices[1][j]);
}
if (!isScreen) shape.transform(viewPort);
shape = clip(shape);
graphics.fill(shape);
}
}
/**
* Fill a shape onto the chart
* @param vertices The vertices of the shape. A 2-Dimensional array.
* The first dimension contains the longitude values of the shape
* The second dimension contains the latitude values of the shape
* @param colour The colour of the shape
* @param graphics The java2d graphics object supplied by the
* plotting/printing medium
* @throws VisADException
*/
public void fillShape(float[][] vertices, Color colour, Graphics2D graphics)
throws VisADException {
// Reproject the lats/lons into device coordinates
CoordinateSystem coordSys = getCoordinateSystem();
// Filter out shapes which straddle the discontinuity of
// a mercator projection.
boolean crosses = crossesDiscontinuity(coordSys, vertices);
if (!crosses) {
float[][] reprojected = coordSys.toReference(vertices);
// Fill the shape in device coordinates
fillShapeReprojected(reprojected, colour, graphics);
}
}
/**
* Test a polygon to see if it crosses the discontinuity of a
* Mercator projection. Shapes that do this, streak across the entire
* width of the chart and need to be filtered out
* @param coordSys The CoordinateSystem of the chart
* @param vertices The vertices of the polygon being tested
* @return true if the shape crosses the discontinuity of a
* Mercator projection
*/
private boolean crossesDiscontinuity(CoordinateSystem coordSys,
float[][] vertices) {
boolean crosses = false;
/*TODO:
if (coordSys instanceof visad.earthmap.MercatorCoordinateSystem) {
visad.earthmap.MercatorCoordinateSystem merc =
(visad.earthmap.MercatorCoordinateSystem) coordSys;
double disco = merc.getCentreLongitude() + 180.0;
float[] lonRange = range(vertices[0]);
if ((lonRange[0] < disco) && (lonRange[1] > disco)) {
return true;
}
}*/
return crosses;
}
/**
* Get the range of an array of floats
* @param array
* @return
*/
private float[] range(float[] array) {
int numPoints = array.length;
if (numPoints <= 1) {
float[] range = {array[0], array[0]};
return range;
}
float[] clone = (float[])array.clone();
java.util.Arrays.sort(clone);
float[] range = new float[2];
range[0] = clone[0];
range[1] = clone[clone.length - 1];
return range;
}
/**
* Fill a reprojected shape from a texture
*
* @param vertices the shape vertices
* @param texture The hatching texture to fill the shape with
* can be Hatching.DIAGONAL1, Hatching.DIAGONAL2,
* Hatching.DIAGONAL_BOTH, Hatching.HORIZONTAL = 3, Hatching.VERTICAL
* or Hatching.SQUARE
* @param graphics The java2d graphics object supplied by the
* plotting/printing medium
*/
public void fillShapeReprojected(float[][] vertices, int texture,
Graphics2D graphics) {
fillShapeReprojected(vertices, texture, graphics, false);
}
/**
* Fill a reprojected shape from a texture
*
* @param vertices the shape vertices
* @param texture The hatching texture to fill the shape with
* can be Hatching.DIAGONAL1, Hatching.DIAGONAL2,
* Hatching.DIAGONAL_BOTH, Hatching.HORIZONTAL = 3, Hatching.VERTICAL
* or Hatching.SQUARE
* @param graphics The java2d graphics object supplied by the
* plotting/printing medium
* @param isScreen true if vertices are in screen (AWT) coordinates
*/
public void fillShapeReprojected(float[][] vertices, int texture,
Graphics2D graphics, boolean isScreen) {
BufferedImage fillTexture = hatching.getPattern(texture);
Rectangle anchor = new Rectangle(30, 30);
TexturePaint hatching = new TexturePaint(fillTexture, anchor);
graphics.setPaint(hatching);
GeneralPath shape = new GeneralPath();
shape.moveTo(vertices[0][0], vertices[1][0]);
for (int j = 1; j < vertices[0].length; j++) {
shape.lineTo(vertices[0][j], vertices[1][j]);
}
graphics.setPaint(hatching);
if (!isScreen) shape.transform(viewPort);
shape = clip(shape);
graphics.fill(shape);
}
/**
* Fill a shape onto the chart
* @param data The vertices of the shape. A 2-Dimensional array.
* The first dimension contains the longitude values of the shape
* The second dimension contains the latitude values of the shape
* @param texture The hatching texture to fill the shape with
* can be Hatching.DIAGONAL1, Hatching.DIAGONAL2,
* Hatching.DIAGONAL_BOTH, Hatching.HORIZONTAL = 3, Hatching.VERTICAL
* or Hatching.SQUARE
* @param graphics The java2d graphics object supplied by the
* plotting/printing medium
* @throws VisADException problem transforming from lat/lon
* to display space
*/
public void fillShape(float[][] data, int texture, Graphics2D graphics)
throws VisADException {
CoordinateSystem coordSys = getCoordinateSystem();
float[][] reprojected = coordSys.toReference(data);
fillShapeReprojected(reprojected, texture, graphics);
}
/**
* Draw text onto the chart
* @param text The text to draw onto the chart
* @param font The font of the text
* @param colour The colour of the text
* @param x The x position of the text (longitude)
* @param y The y position of the text (latitude)
* @param graphics The java2d graphics object supplied by the
* plotting/printing medium
* @throws VisADException
*/
public void drawString(String text, Font font, Color colour, float x,
float y, Graphics2D graphics)
throws VisADException {
if (text.length() < 1) {
return;
}
graphics.setColor(colour);
// Scale the font to match the pixel size of the display
int size = font.getSize();
Font scaledFont = font.deriveFont(size * pixelWidth);
double[][] coords = new double[3][1];
coords[0][0] = x;
coords[1][0] = y;
coords[2][0] = 0.0f;
graphics.setFont(scaledFont);
// Reproject the user coordinates to Normal X, Y coordinates
CoordinateSystem coordSys = getCoordinateSystem();
double[][] reprojected = coordSys.toReference(coords);
double[] normCoords = {reprojected[0][0], reprojected[1][0]};
float[] devCoords = new float[2];
viewPort.transform(normCoords, 0, devCoords, 0, 1);
if ((devCoords[0] > 0) && (devCoords[0] < width) && (devCoords[1] > 0) &&
(devCoords[1] < height)) {
graphics.drawString(text, devCoords[0], devCoords[1]);
}
}
/**
* Extract the Geometries from the Visad display and render them to the
* Graphics2D
*
* @param display the display to copy
* @param graphics the graphics to render to
*/
private void copyVisadDisplay(DisplayImpl display, Graphics2D graphics) {
int mode = -1;
if (display instanceof DisplayImplJ3D) {
mode = MODE_3D;
}
else if (display instanceof DisplayImplJ2D) {
mode = MODE_2D;
}
DisplayRenderer displayRenderer = null;
Object root = null;
// If the display is 2D
if (mode == MODE_2D) {
// Get the root VisADGroup from the display renderer
displayRenderer = (DisplayRendererJ2D)display.getDisplayRenderer();
root = (VisADGroup)((DisplayRendererJ2D)displayRenderer).getRoot();
}
else {
// Otherwise get the Java3d Group
displayRenderer = (DisplayRendererJ3D)display.getDisplayRenderer();
root = (Group)((DisplayRendererJ3D)displayRenderer).getRoot();
}
/*
try {
displayRenderer.setBoxOn(false);
displayRenderer.setScaleOn(false);
} catch (VisADException e) {
System.err.println("VectorPlotter.generate: " + e);
} catch (RemoteException e) {
System.err.println("VectorPlotter.generate: " + e);
}
*/
// Recurse the plotDisplay, converting all plotted
// objects to vectors rendered through a Graphics2D object
if (mode == MODE_2D) {
copyGroup((VisADGroup)root, graphics);
}
else if (mode == MODE_3D) {
copyGroup((Group)root, graphics);
}
}
/**
* Recursively process each VisAD group in the display, If the group is
* a geometry array, stop recursing and plot it
*
* @param root - The root VisADGroup from which to recurse
* @param graphics the graphics to render to
*/
private void copyGroup(VisADGroup root, Graphics2D graphics) {
// Loop over eah of the VisAD groups children
for (int i = 0; i < root.numChildren(); i++) {
// Get the next Child
VisADSceneGraphObject child = root.getChild(i);
// If this child is a VisADAppearance
if (child instanceof VisADAppearance) {
VisADAppearance appearance = (VisADAppearance)child;
// Plot it's vertices
VisADGeometryArray geometry = appearance.array;
Color[] colours = getColours(appearance, monochrome);
float fsize = (geometry instanceof VisADPointArray)
? appearance.pointSize
: appearance.lineWidth;
float thickness = fsize / 2.0f;
plot(geometry, colours, thickness, graphics);
}
// If this child is a VisADGroup
if (child instanceof VisADGroup) {
// Recurse this group
copyGroup((VisADGroup)child, graphics);
}
}
}
/**
* Recursively process each Java3d group in the display, If the group is
* a geometry array, stop recursing and plot it
*
* @param root - The root Java 3D Group from which to recurse
* @param graphics the graphics to render to
*/
private void copyGroup(Group root, Graphics2D graphics) {
int numChildren = 0;
if (root.getCapability(Group.ALLOW_CHILDREN_READ)) {
numChildren = root.numChildren();
}
// Check to see which children are rendered
int rendered = -1;
if (root instanceof Switch) {
rendered = ((Switch)root).getWhichChild();
if (rendered == -1) return; // means it's not rendered?
}
// Loop over each of the VisAD groups children
for (int i = 0; i < numChildren; i++) {
Node child = root.getChild(i);
// Only render this node if it is Switched on
if ((rendered >= 0) && (rendered != i)) {
continue;
}
if (child instanceof Group) {
// Todo
// Check for Switches here to support layers?
copyGroup((Group)child, graphics);
}
else if (child instanceof Shape3D) {
Shape3D shape = (Shape3D)child;
int numGeoms = 0;
if (shape.getCapability(Shape3D.ALLOW_GEOMETRY_READ)) {
numGeoms = shape.numGeometries();
}
Appearance appearance = shape.getAppearance();
Color[] colours = getColours(appearance);
float thickness = getLineThickness(appearance);
int lineStyle = getLineStyle(appearance);
Texture texture = appearance.getTexture();
for (int j = 0; j < numGeoms; j++) {
GeometryArray geom = (GeometryArray)shape.getGeometry(j);
plot(geom, colours, thickness, texture, lineStyle, graphics);
}
}
else {
// System.err.println ("Unknown scene graph node:" + child.getClass().getName());
}
}
}
/**
* Get the colours from the Appearance
*
* @param appearance the appearance
* @param monochrome true for monochrome
*
* @return the array of colours for the Appearance
*/
private Color[] getColours(VisADAppearance appearance, boolean monochrome) {
Color[] colours = null;
VisADGeometryArray geometry = appearance.array;
// If the geometry stores it's colours ...
if (geometry.colors != null) {
// Get each individual colour
int numColours = geometry.colors.length;
int numCoords = geometry.coordinates.length;
// Get the ratio of colors to points to distinguish RGB
// from RGBA. This is a hack until I understand why some
// LineArrays from a 2D display contain Alpha values
int cr = 3;
if (numColours != numCoords) {
cr = numColours / (numColours - numCoords);
}
colours = new Color[numColours / cr];
for (int j = 0; j < numColours; j += cr) {
float red = 0.0f;
float green = 0.0f;
float blue = 0.0f;
if (!monochrome) {
red = byteToFloat(geometry.colors[j]);
green = byteToFloat(geometry.colors[j + 1]);
blue = byteToFloat(geometry.colors[j + 2]);
}
colours[j / cr] = new Color(red, green, blue);
}
}
else {
// Otherwise fill the array with the global colour
float red = 0.0f;
float green = 0.0f;
float blue = 0.0f;
if (!monochrome) {
red = appearance.red;
green = appearance.green;
blue = appearance.blue;
}
colours = new Color[1];
colours[0] = new Color(red, green, blue);
}
return colours;
}
/**
* Get the colors from the Appearance
*
* @param appearance the Appearance
*
* @return the colours
*/
private Color[] getColours(Appearance appearance) {
Color[] colours = null;
ColoringAttributes colourAttr = appearance.getColoringAttributes();
int colourFlag = ColoringAttributes.ALLOW_COLOR_READ;
TransparencyAttributes transAttr = appearance.getTransparencyAttributes();
int transMFlag = TransparencyAttributes.ALLOW_MODE_READ;
int transVFlag = TransparencyAttributes.ALLOW_VALUE_READ;
if ((colourAttr != null) && (colourAttr.getCapability(colourFlag))) {
Color3f color3f = new Color3f();
colourAttr.getColor(color3f);
colours = new Color[1];
Color color3 = color3f.get();
float[] colourComps = new float[4];
colourComps = color3.getColorComponents(colourComps);
colourComps[3] = 1.0f;
/*
if (transAttr != null &&
transAttr.getCapability(transMFlag) &&
transAttr.getCapability(transVFlag) &&
transAttr.getTransparencyMode() != TransparencyAttributes.NONE) {
colourComps[3] = transAttr.getTransparency();
}
*/
colours[0] = new Color(colourComps[0], colourComps[1], colourComps[2],
colourComps[3]);
}
return colours;
}
/**
* Get the line thickness from the Appearance
*
* @param appearance the Appearance
*
* @return the line thickness
*/
private float getLineThickness(Appearance appearance) {
float thickness = 0.0f;
LineAttributes lineAttr = appearance.getLineAttributes();
if (lineAttr != null && lineAttr.getCapability(LineAttributes.ALLOW_WIDTH_READ)) {
thickness = lineAttr.getLineWidth();
}
return thickness;
}
/**
* Get the line style from the Appearance
*
* @param appearance the Appearance
*
* @return the line style
*/
private int getLineStyle(Appearance appearance) {
LineAttributes lineAttr = appearance.getLineAttributes();
int lineStyle = LineAttributes.PATTERN_SOLID;
if (lineAttr != null && lineAttr.getCapability(LineAttributes.ALLOW_PATTERN_READ)) {
lineStyle = lineAttr.getLinePattern();
}
return lineStyle;
}
/**
* Convert an unsigned byte into a float
*
* @param byteVal - A number between 0 and 255
* @return the number converted to a signed floating point number
*/
private float byteToFloat(byte byteVal) {
float floatVal = 0.0f;
if (byteVal >= 0) {
floatVal = ((float)byteVal) / 256.0f;
}
else {
floatVal = ((float)(byteVal + 256)) / 256.0f;
}
return floatVal;
}
/**
* Convert a geometry array into plottable vectors
*
* @param geometryArray The definition of the shape of the object to be
* plotted
* @param colours A list of the colours of each vertex of the object
* @param thickness The line thickness with which to draw the object
* @param graphics The graphics to plot to
*/
private void plot(VisADGeometryArray geometryArray, Color[] colours,
float thickness, Graphics2D graphics) {
// Draw a VisADPointArray
if (geometryArray instanceof VisADPointArray) {
VisADPointArray pointArray = (VisADPointArray)geometryArray;
plot(pointArray, colours, thickness, graphics);
// Draw a VisADLineStripArray
}
else if (geometryArray instanceof VisADLineStripArray) {
VisADLineStripArray lineArray = (VisADLineStripArray)geometryArray;
plot(lineArray, colours, thickness, graphics);
// Draw a VisADLineArray
}
else if (geometryArray instanceof VisADLineArray) {
VisADLineArray lineArray = (VisADLineArray)geometryArray;
plot(lineArray, colours, thickness, graphics);
// Draw a VisADTriangleStripArray
}
else if (geometryArray instanceof VisADTriangleStripArray) {
VisADTriangleStripArray triangleArray =
(VisADTriangleStripArray)geometryArray;
plot(triangleArray, colours, thickness, graphics);
// Draw a VisADIndexedTriangleStripArray
}
else if (geometryArray instanceof VisADIndexedTriangleStripArray) {
VisADIndexedTriangleStripArray triangleArray =
(VisADIndexedTriangleStripArray)geometryArray;
plot(triangleArray, colours, thickness, graphics);
// Draw a VisADTriangleArray
}
else if (geometryArray instanceof VisADTriangleArray) {
VisADTriangleArray triangleArray = (VisADTriangleArray)geometryArray;
plot(triangleArray, colours, thickness, graphics);
}
else {
// Other geometries go here
}
}
/**
* Convert a geometry array into plottable vectors
*
* @param geometryArray The definition of the shape of the object to be
* plotted
* @param colours A list of the colours of each vertex of the object
* @param thickness The line thickness with which to draw the object
* @param texture The hatching texture
* @param lineStyle The line style
* @param graphics The graphics to plot to
*/
private void plot(GeometryArray geometryArray, Color[] colours,
float thickness, Texture texture, int lineStyle,
Graphics2D graphics) {
//System.err.println("plot:" + geometryArray.getClass().getName());
if (geometryArray instanceof LineArray) {
LineArray lineArray = (LineArray)geometryArray;
plot(lineArray, colours, thickness, lineStyle, graphics);
}
else if (geometryArray instanceof TriangleArray) {
TriangleArray triangleArray = (TriangleArray)geometryArray;
plot(triangleArray, colours, thickness, graphics);
}
else if (geometryArray instanceof QuadArray) {
QuadArray quadArray = (QuadArray)geometryArray;
if (texture == null) {
plot(quadArray, colours, thickness, graphics);
}
else {
//Don't do textures
}
}
else if (geometryArray instanceof LineStripArray) {
LineStripArray lineStripArray = (LineStripArray)geometryArray;
plot(lineStripArray, colours, thickness, lineStyle, graphics);
}
else if (geometryArray instanceof TriangleStripArray) {
TriangleStripArray triangleArray = (TriangleStripArray)geometryArray;
// Treat geometries with texture separately
if (texture == null) {
plot(triangleArray, colours, thickness, graphics);
}
}
else if (geometryArray instanceof IndexedTriangleStripArray) {
IndexedTriangleStripArray triangleArray =
(IndexedTriangleStripArray)geometryArray;
plot(triangleArray, colours, thickness, graphics);
} // Draw a VisADPointArray
else if (geometryArray instanceof PointArray) {
PointArray pointArray = (PointArray)geometryArray;
plot(pointArray, colours, thickness, graphics);
}
else {
// Other geometries go here
// System.err.println ("Unknown geometry:" + geometryArray.getClass().getName());
}
}
/**
* Plot a VisADPointArray into postscript format
*
* @param pointArray The pointArray to be plotted
* @param colours The colour to plot the points
* @param size Size of the points
* @param graphics The graphics to plot to
*/
private void plot(VisADPointArray pointArray, Color[] colours, float size,
Graphics2D graphics) {
graphics.setColor(colours[0]);
graphics.setStroke(getStroke(pixelWidth));
float[] coordinates = pointArray.coordinates;
float hsize = 0.5f * size;
// Loop over each point
for (int i = 0; i < coordinates.length / 3; i++) {
float normalX = coordinates[i * 3];
float normalY = coordinates[i * 3 + 1];
//graphics.fillRect((int)normalX, (int)normalY, (int)size, (int)size);
graphics.fill(
new Rectangle2D.Float(normalX - hsize, normalY - hsize, size, size));
}
}
/**
* Convert a VisADLineStripArray into plottable vectors
*
* @param lineArray The VisADLineStripArray
* @param colours The list of colors
* @param thickness The line thickness
* @param graphics The graphics
*/
private void plot(VisADLineStripArray lineArray, Color[] colours,
float thickness, Graphics2D graphics) {
graphics.setColor(colours[0]);
graphics.setStroke(getStroke(thickness));
float[] coordinates = lineArray.coordinates;
// Get the sizes of all the "chunks"
int[] vertexCounts = lineArray.stripVertexCounts;
int base = 0;
// Loop over each chunk
for (int i = 0; i < vertexCounts.length; i++) {
int numCoords = vertexCounts[i];
if (i < colours.length) {
graphics.setColor(colours[i]);
}
GeneralPath path = new GeneralPath();
// Store the starting position of this chunk
path.moveTo(coordinates[base], coordinates[base + 1]);
boolean visible = false;
float nxLast = coordinates[base];
float nyLast = coordinates[base + 1];
// Loop over all the points in this chunk
for (int j = 0; j < numCoords; j++) {
// Get the (normalised) display coordinates
float nX = coordinates[base + j * 3];
float nY = coordinates[base + j * 3 + 1];
// If the line is visible, draw it
if (visible(nxLast, nyLast, nX, nY, graphics)) {
visible = true;
}
path.lineTo((float)nX, (float)nY);
nxLast = nX;
nyLast = nY;
}
if (visible) {
// Convert display coordinates to device coords
path.transform(viewPort);
graphics.draw(path);
}
base += 3 * numCoords;
}
}
/**
* Convert a VisADLineArray into plottable vectors
*
* @param lineArray The VisADLineArray
* @param colours The list of colors
* @param thickness The line thickness
* @param graphics The graphics
*/
private void plot(VisADLineArray lineArray, Color[] colours,
float thickness, Graphics2D graphics) {
graphics.setColor(colours[0]);
graphics.setStroke(getStroke(thickness));
float[] coordinates = lineArray.coordinates;
int numCoords = lineArray.vertexCount;
for (int j = 0; j < numCoords; j += 2) {
if (j < colours.length) {
graphics.setColor(colours[j]);
}
// Get (normalised) display coordinates
float nX1 = coordinates[j * 3];
float nY1 = coordinates[j * 3 + 1];
float nX2 = coordinates[j * 3 + 3];
float nY2 = coordinates[j * 3 + 4];
// If the line is visible, draw it
if (visible(nX1, nY1, nX2, nY2, graphics)) {
GeneralPath path = new GeneralPath();
path.moveTo(nX1, nY1);
path.lineTo(nX2, nY2);
// Convert them to device coords, and plot
path.transform(viewPort);
graphics.draw(path);
}
}
}
/**
* Convert a VisADTriangleStripArray into plottable vectors
*
*
* @param triangleArray The VisADTriangleStripArray
* @param colours The list of colors at each vertex
* @param thickness The line thickness
* @param graphics The graphics
*/
private void plot(VisADTriangleStripArray triangleArray, Color[] colours,
float thickness, Graphics2D graphics) {
graphics.setColor(colours[0]);
graphics.setStroke(getStroke(thickness));
float[] coordinates = triangleArray.coordinates;
// Get the sizes of all the "chunks"
int[] vertexCounts = triangleArray.stripVertexCounts;
int base = 0;
// Loop over each chunk
for (int i = 0; i < vertexCounts.length; i++) {
int numCoords = vertexCounts[i];
// Store the starting position of this chunk
float normalLastX = coordinates[base];
float normalLastY = coordinates[base + 1];
GeneralPath path = new GeneralPath();
path.moveTo(normalLastX, normalLastY);
// Loop over all the points in this chunk
for (int j = 0; j < numCoords; j++) {
float normalX = coordinates[base + j * 3];
float normalY = coordinates[base + j * 3 + 1];
normalLastX = normalX;
normalLastY = normalY;
path.lineTo(normalX, normalY);
}
path.closePath();
path.transform(viewPort);
graphics.fill(path);
base += 3 * numCoords;
}
}
/**
* Convert a VisADTriangleArray into plottable vectors
*
* @param triangleArray The VisADTriangleArray
* @param colours The list of colors at each vertex
* @param thickness The line thickness
* @param graphics The graphics
*/
private void plot(VisADTriangleArray triangleArray, Color[] colours,
float thickness, Graphics2D graphics) {
float[] colour = new float[4];
if (colours != null) {
colour[0] = ((float)colours[0].getRed()) / 255.0f;
colour[1] = ((float)colours[0].getGreen()) / 255.0f;
colour[2] = ((float)colours[0].getBlue()) / 255.0f;
colour[3] = ((float)colours[0].getAlpha()) / 255.0f;
if (!useTransparency) {
colour[3] = 1.0f;
}
}
graphics.setStroke(getStroke(thickness));
int vertexCount = triangleArray.vertexCount;
float[] coordinates = triangleArray.coordinates;
for (int i = 0; i < 3 * vertexCount; i += 9) {
// If monochrome
if (monochrome) {
// Set everything except white to black
monochromatise(colour);
}
Color color = new Color(colour[0], colour[1], colour[2], colour[3]);
graphics.setColor(color);
float[][] vertices = new float[3][3];
vertices[0][0] = coordinates[i];
vertices[1][0] = coordinates[i + 1];
vertices[0][1] = coordinates[i + 3];
vertices[1][1] = coordinates[i + 4];
vertices[0][2] = coordinates[i + 6];
vertices[1][2] = coordinates[i + 7];
int clockwise = clockwise(vertices[0], vertices[1]);
if (clockwise >= 0) {
vertices[0] = reverseDirection(vertices[0]);
vertices[1] = reverseDirection(vertices[1]);
}
fillShapeReprojected(vertices, color, graphics);
}
}
/**
* Convert a VisADIndexedTriangleStripArray into plottable vectors
*
* @param triangleArray The VisADIndexedTriangleStripArray
* @param colours The list of colors at each vertex
* @param thickness The line thickness
* @param graphics The graphics
*/
private void plot(VisADIndexedTriangleStripArray triangleArray,
Color[] colours, float thickness, Graphics2D graphics) {
graphics.setColor(colours[0]);
graphics.setStroke(getStroke(thickness));
float[] coordinates = triangleArray.coordinates;
int[] indices = triangleArray.indices;
int[] stripVertexCounts = triangleArray.stripVertexCounts;
int base = 0;
for (int strip = 0; strip < stripVertexCounts.length; strip++) {
if (strip < colours.length) {
graphics.setColor(colours[strip]);
}
int count = stripVertexCounts[strip];
int index0 = indices[base];
int index1 = indices[base + 1];
GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
boolean visible = false;
for (int i = base + 2; i < base + count; i++) {
int index2 = indices[i];
float normalX0 = coordinates[3 * index0];
float normalY0 = coordinates[3 * index0 + 1];
float normalX1 = coordinates[3 * index1];
float normalY1 = coordinates[3 * index1 + 1];
float normalX2 = coordinates[3 * index2];
float normalY2 = coordinates[3 * index2 + 1];
// If any of the triangle is within the
// area of interest, plot it
// if (visible(xCoords, yCoords, graphics)) {
path.moveTo(normalX0, normalY0);
path.lineTo(normalX1, normalY1);
path.lineTo(normalX2, normalY2);
visible = true;
// }
index0 = index1;
index1 = index2;
}
if (visible) {
path.transform(viewPort);
path.closePath();
//////// graphics.fill(path);
}
base += count;
}
}
/**
* Convert a LineStripArray into plottable vectors
*
* @param lineArray The LineStripArray
* @param colours The list of colors at each vertex
* @param thickness The line thickness
* @param lineStyle The line style
* @param graphics The graphics
*/
private void plot(LineStripArray lineArray, Color[] colours,
float thickness, int lineStyle, Graphics2D graphics) {
// Temporary variables for retrieving coordinates
int vertexCount = lineArray.getVertexCount();
boolean hasAlpha = hasAlpha(lineArray);
boolean byRef = isByReference(lineArray);
int nCoords = 3 * vertexCount;
float[] coordinates = null;
// Get the coordinates
if (byRef) {
coordinates = lineArray.getCoordRefFloat();
}
else {
coordinates = new float[nCoords];
lineArray.getCoordinates(0, coordinates);
}
coordinates = transformToScreen(coordinates);
int numStrips = lineArray.getNumStrips();
int[] vertexCounts = new int[numStrips];
lineArray.getStripVertexCounts(vertexCounts);
int base = 0;
int baseColor = 0;
float[] colour = new float[4];
byte[] refColours = null;
int numRefColours = (hasAlpha)
? 4
: 3;
// If a colour was supplied, use it
if (colours != null) {
colour[0] = ((float)colours[0].getRed()) / 255.0f;
colour[1] = ((float)colours[0].getGreen()) / 255.0f;
colour[2] = ((float)colours[0].getBlue()) / 255.0f;
colour[3] = ((float)colours[0].getAlpha()) / 255.0f;
}
else {
// Get the color array if by ref
if (byRef) {
// DisplayImplJ3D stores ref as bytes
refColours = lineArray.getColorRefByte();
for (int i = 0; i < numRefColours; i++) {
colour[i] = byteToFloat(refColours[i]);
}
}
else {
lineArray.getColor(0, colour);
}
}
if (!hasAlpha || !useTransparency) {
colour[3] = 1.0f;
}
// If monochrome
if (monochrome) {
// Set everything except white to black
monochromatise(colour);
}
graphics.setStroke(
getStroke(thickness, getStrokeDash(lineStyle, pixelWidth)));
Color lastColor = new Color(colour[0], colour[1], colour[2], colour[3]);
// Loop over each chunk
for (int i = 0; i < vertexCounts.length; i++) {
int numCoords = vertexCounts[i];
linePath = new GeneralPath();
graphics.setColor(lastColor);
//VisAD stores strips as one complete section so we have to draw each
//segment
for (int seg = 0; seg < numCoords-1; seg++) {
// Attempt to get the color from the geometry
if (colours == null) {
// Get the color array
if (refColours != null) {
for (int j = 0; j < numRefColours; j++) {
colour[j] = byteToFloat(refColours[baseColor + j]);
}
}
else {
lineArray.getColor(base, colour);
}
}
if (!useTransparency || !hasAlpha) {
colour[3] = 1.0f;
}
// If monochrome
if (monochrome) {
// Set everything except white to black
monochromatise(colour);
}
Color color = new Color(colour[0], colour[1], colour[2], colour[3]);
// Draw lines of one colour in a sigle GeneralPath
// This makes the resulting image MUCH more slick
// when plotting many small lines (eg. observations)
if (!color.equals(lastColor)) {
graphics.setColor(lastColor);
lastColor = color;
if (!transformToScreenCoords) linePath.transform(viewPort);
//linePath = clip(linePath);
graphics.draw(linePath);
linePath = new GeneralPath();
}
// Get the (normalised) display coordinates
float nX1 = coordinates[base];
float nY1 = coordinates[base + 1];
float nX2 = coordinates[base + 3];
float nY2 = coordinates[base + 4];
if (visible(nX1, nY1, nX2, nY2, graphics, transformToScreenCoords)) {
linePath.moveTo(nX1, nY1);
linePath.lineTo(nX2, nY2);
}
base += 3;
baseColor += numRefColours;
}
// Translate them to device coordinates and plot
if (!transformToScreenCoords) linePath.transform(viewPort);
//linePath = clip(linePath);
graphics.draw(linePath);
base += 3;
baseColor += numRefColours;
}
}
/**
* Convert a LineArray into plottable vectors
*
* @param lineArray The LineStripArray
* @param colours The list of colors at each vertex
* @param thickness The line thickness
* @param lineStyle The line style
* @param graphics The graphics
*/
private void plot(LineArray lineArray, Color[] colours, float thickness,
int lineStyle, Graphics2D graphics) {
boolean hasAlpha = hasAlpha(lineArray);
boolean byRef = isByReference(lineArray);
float[] colour = new float[4];
byte[] refColours = null;
int numRefColours = (hasAlpha)
? 4
: 3;
int baseColor = 0;
// If a colour was supplied, use it
if (colours != null) {
colour[0] = ((float)colours[0].getRed()) / 255.0f;
colour[1] = ((float)colours[0].getGreen()) / 255.0f;
colour[2] = ((float)colours[0].getBlue()) / 255.0f;
colour[3] = ((float)colours[0].getAlpha()) / 255.0f;
}
else {
//lineArray.getColor(0, colour);
if (byRef) {
// DisplayImplJ3D stores ref as bytes
refColours = lineArray.getColorRefByte();
for (int i = 0; i < numRefColours; i++) {
colour[i] = byteToFloat(refColours[i]);
}
}
else {
lineArray.getColor(0, colour);
}
}
if (!useTransparency || !hasAlpha) {
colour[3] = 1.0f;
}
// If monochrome
if (monochrome) {
// Set everything except white to black
monochromatise(colour);
}
/*
if (lineStyle == LineAttributes.PATTERN_DASH) {
// float[] dash = { 24.0f * pixelWidth, 8.0f * pixelWidth };
float size = (float) Math.sqrt(width * width + height * height);
float scale = size / 3000.0f;
// System.err.println(size + "/" + scale + "Width: " + width + "/"
// + height);
float[] dash = { 18.0f * scale, 6.0f * scale };
graphics.setStroke(getStroke(thickness * .5f, dash));
} else {
graphics.setStroke(getStroke(thickness * .5f));
}
*/
//graphics.setStroke(
// getStroke(thickness * .5f, getStrokeDash(lineStyle, pixelWidth)));
graphics.setStroke(
getStroke(thickness, getStrokeDash(lineStyle, pixelWidth)));
// Temporary variables for retrieving coordinates
int vertexCount = lineArray.getVertexCount();
int nCoords = 3 * vertexCount;
float[] coordinates = null;
// Get the coordinates
if (byRef) {
coordinates = lineArray.getCoordRefFloat();
}
else {
coordinates = new float[nCoords];
lineArray.getCoordinates(0, coordinates);
}
/*
float[] coordinates = new float[vertexCount * 3];
lineArray.getCoordinates(0, coordinates);
*/
coordinates = transformToScreen(coordinates);
Color lastColor = new Color(colour[0], colour[1], colour[2], colour[3]);
linePath = new GeneralPath();
graphics.setColor(lastColor);
for (int j = 0; j < vertexCount; j += 2) {
if (colours == null) {
//lineArray.getColor(i, colour);
if (refColours != null) { // means we are BY_REFERENCE
for (int i = 0; i < numRefColours; i++) {
colour[i] = byteToFloat(refColours[baseColor + i]);
}
}
else {
lineArray.getColor(j, colour);
}
}
if (!useTransparency || !hasAlpha) {
colour[3] = 1.0f;
}
// If monochrome
if (monochrome) {
// Set everything except white to black
monochromatise(colour);
}
Color color = new Color(colour[0], colour[1], colour[2], colour[3]);
// Draw lines of one colour in a sigle GeneralPath
// This makes the resulting image MUCH more slick
// when plotting many small lines (eg. observations)
if (!color.equals(lastColor)) {
graphics.setColor(lastColor);
lastColor = color;
if (!transformToScreenCoords) linePath.transform(viewPort);
//linePath = clip(linePath);
graphics.draw(linePath);
linePath = new GeneralPath();
}
// Get the (normalised) display coordinates
float nX1 = coordinates[j * 3];
float nY1 = coordinates[j * 3 + 1];
float nX2 = coordinates[j * 3 + 3];
float nY2 = coordinates[j * 3 + 4];
if (!visible(nX1, nY1, nX2, nY2, graphics, transformToScreenCoords)) {
continue;
}
linePath.moveTo(nX1, nY1);
linePath.lineTo(nX2, nY2);
baseColor += numRefColours * 2;
}
// Translate them to device coordinates and plot
if (!transformToScreenCoords) linePath.transform(viewPort);
//linePath = clip(linePath);
graphics.draw(linePath);
}
/**
* Convert a TriangleStripArray into plottable vectors
*
* @param triangleArray The TriangleStripArray
* @param colours The list of colors at each vertex
* @param thickness The line thickness
* @param graphics The graphics
*/
private void plot(TriangleStripArray triangleArray, Color[] colours,
float thickness, Graphics2D graphics) {
int vertexFormat = triangleArray.getVertexFormat();
if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_2)
== GeometryArray.TEXTURE_COORDINATE_2) {
return;
}
boolean hasAlpha = hasAlpha(triangleArray);
boolean byRef = isByReference(triangleArray);
byte[] refColours = null;
int numRefColours = (hasAlpha)
? 4
: 3;
int baseColor = 0;
float[] colour = new float[4];
if (colours != null) {
colour[0] = ((float)colours[0].getRed()) / 255.0f;
colour[1] = ((float)colours[0].getGreen()) / 255.0f;
colour[2] = ((float)colours[0].getBlue()) / 255.0f;
colour[3] = ((float)colours[0].getAlpha()) / 255.0f;
}
else {
if (byRef) {
// DisplayImplJ3D stores ref as bytes
refColours = triangleArray.getColorRefByte();
}
}
if (!useTransparency || !hasAlpha) {
colour[3] = 1.0f;
}
// If monochrome
if (monochrome) {
// Set everything except white to black
monochromatise(colour);
}
graphics.setStroke(getStroke(thickness));
// Temporary variables for retrieving coordinates
int vertexCount = triangleArray.getVertexCount();
int nCoords = 3 * vertexCount;
float[] coordinates = null;
// Get the coordinates
if (byRef) {
coordinates = triangleArray.getCoordRefFloat();
}
else {
coordinates = new float[nCoords];
triangleArray.getCoordinates(0, coordinates);
}
coordinates = transformToScreen(coordinates);
int cCount = 0;
// Find out how many strips
int numStrips = triangleArray.getNumStrips();
// Get the sizes of each strip
int[] vertexCounts = new int[numStrips];
triangleArray.getStripVertexCounts(vertexCounts);
int base = 0;
// Loop over each strip
for (int i = 0; i < numStrips; i++) {
int numCoords = vertexCounts[i];
if (colours == null) {
//triangleArray.getColor(cCount++, colour);
if (refColours != null) { // means we are BY_REFERENCE
for (int j = 0; j < numRefColours; j++) {
colour[j] = byteToFloat(refColours[baseColor + j]);
}
}
else {
triangleArray.getColor(cCount++, colour);
}
}
if (!useTransparency || !hasAlpha) {
colour[3] = 1.0f;
}
// If monochrome
if (monochrome) {
// Set everything except white to black
monochromatise(colour);
}
Color color = new Color(colour[0], colour[1], colour[2], colour[3]);
graphics.setColor(color);
float lastNormX2 = coordinates[base];
float lastNormY2 = coordinates[base + 1];
float lastNormX1 = coordinates[base + 3];
float lastNormY1 = coordinates[base + 3 + 1];
for (int j = 2; j < numCoords; j++) {
float normalX = coordinates[base + j * 3];
float normalY = coordinates[base + j * 3 + 1];
float[][] triangle = new float[3][3];
triangle[0][0] = lastNormX1;
triangle[1][0] = lastNormY1;
triangle[0][1] = lastNormX2;
triangle[1][1] = lastNormY2;
triangle[0][2] = normalX;
triangle[1][2] = normalY;
lastNormX2 = lastNormX1;
lastNormX1 = normalX;
lastNormY2 = lastNormY1;
lastNormY1 = normalY;
fillShapeReprojected(
triangle, color, graphics, transformToScreenCoords);
}
base += 3 * numCoords;
baseColor += numRefColours * numCoords;
}
}
/**
* Convert a TriangleArray into plottable vectors
*
* @param triangleArray The TriangleArray
* @param colours The list of colors at each vertex
* @param thickness The line thickness
* @param graphics The graphics
*/
private void plot(TriangleArray triangleArray, Color[] colours,
float thickness, Graphics2D graphics) {
boolean hasAlpha = hasAlpha(triangleArray);
boolean byRef = isByReference(triangleArray);
byte[] refColours = null;
int numRefColours = (hasAlpha)
? 4
: 3;
int baseColor = 0;
float[] colour = new float[4];
if (colours != null) {
colour[0] = ((float)colours[0].getRed()) / 255.0f;
colour[1] = ((float)colours[0].getGreen()) / 255.0f;
colour[2] = ((float)colours[0].getBlue()) / 255.0f;
colour[3] = ((float)colours[0].getAlpha()) / 255.0f;
}
else {
//triangleArray.getColor(0, colour);
if (byRef) {
// DisplayImplJ3D stores ref as bytes
refColours = triangleArray.getColorRefByte();
}
}
if (!hasAlpha || !useTransparency) {
colour[3] = 1.0f;
}
// If monochrome
if (monochrome) {
// Set everything except white to black
monochromatise(colour);
}
graphics.setStroke(getStroke(thickness));
// Temporary variables for retrieving coordinates
int vertexCount = triangleArray.getVertexCount();
int nCoords = 3 * vertexCount;
float[] coordinates = null;
// Get the coordinates
if (byRef) {
coordinates = triangleArray.getCoordRefFloat();
}
else {
coordinates = new float[nCoords];
triangleArray.getCoordinates(0, coordinates);
}
coordinates = transformToScreen(coordinates);
for (int i = 0; i < 3 * vertexCount; i += 9) {
if (colours == null) {
//triangleArray.getColor(cCount++, colour);
if (refColours != null) { // means we are BY_REFERENCE
for (int j = 0; j < numRefColours; j++) {
colour[j] = byteToFloat(refColours[baseColor + j]);
}
}
else {
//if (i < vertexCount * 3) { is this necessary?
triangleArray.getColor(i / 3, colour);
}
}
// If monochrome
if (!useTransparency || !hasAlpha) {
colour[3] = 1.0f;
}
if (monochrome) {
// Set everything except white to black
monochromatise(colour);
}
Color color = new Color(colour[0], colour[1], colour[2], colour[3]);
graphics.setColor(color);
// Get the (normalised) display coordinates
float[][] vertices = new float[2][3];
vertices[0][0] = coordinates[i];
vertices[1][0] = coordinates[i + 1];
vertices[0][1] = coordinates[i + 3];
vertices[1][1] = coordinates[i + 4];
vertices[0][2] = coordinates[i + 6];
vertices[1][2] = coordinates[i + 7];
// Must be clockwise (batik screws up otherwise)
int clockwise = clockwise(vertices[0], vertices[1]);
if (clockwise >= 0) {
vertices[0] = reverseDirection(vertices[0]);
vertices[1] = reverseDirection(vertices[1]);
}
fillShapeReprojected(
vertices, color, graphics, transformToScreenCoords);
baseColor += 3 * numRefColours;
}
}
/**
* Convert a QuadArray into plottable vectors
*
* @param quadArray The QuadArray
* @param colours The list of colors at each vertex
* @param thickness The line thickness
* @param graphics The graphics
*/
private void plot(QuadArray quadArray, Color[] colours, float thickness,
Graphics2D graphics) {
boolean hasAlpha = hasAlpha(quadArray);
boolean byRef = isByReference(quadArray);
byte[] refColours = null;
int numRefColours = (hasAlpha)
? 4
: 3;
int baseColor = 0;
float[] colour = new float[4];
if (colours != null) {
colour[0] = ((float)colours[0].getRed()) / 255.0f;
colour[1] = ((float)colours[0].getGreen()) / 255.0f;
colour[2] = ((float)colours[0].getBlue()) / 255.0f;
colour[3] = ((float)colours[0].getAlpha()) / 255.0f;
}
else {
if (byRef) {
// DisplayImplJ3D stores ref as bytes
refColours = quadArray.getColorRefByte();
}
}
if (!useTransparency || !hasAlpha) {
colour[3] = 1.0f;
}
// If monochrome
if (monochrome) {
// Set everything except white to black
monochromatise(colour);
}
graphics.setStroke(getStroke(thickness));
// Temporary variables for retrieving coordinates
int vertexCount = quadArray.getVertexCount();
int nCoords = 3 * vertexCount;
float[] coordinates = null;
// Get the coordinates
if (byRef) {
coordinates = quadArray.getCoordRefFloat();
}
else {
coordinates = new float[nCoords];
quadArray.getCoordinates(0, coordinates);
}
coordinates = transformToScreen(coordinates);
// System.err.println("quad:" + vertexCount);
for (int i = 0; i < 3 * vertexCount; i += 12) {
if (colours == null) {
//triangleArray.getColor(cCount++, colour);
if (refColours != null) { // means we are BY_REFERENCE
for (int j = 0; j < numRefColours; j++) {
colour[j] = byteToFloat(refColours[baseColor + j]);
}
}
else {
//if (i < vertexCount * 4) { is this necessary?
quadArray.getColor(i / 4, colour);
}
}
if (!useTransparency || !hasAlpha) {
colour[3] = 1.0f;
}
// If monochrome
if (monochrome) {
// Set everything except white to black
monochromatise(colour);
}
Color color = new Color(colour[0], colour[1], colour[2], colour[3]);
graphics.setColor(color);
// Get the (normalised) display coordinates
float[][] vertices = new float[2][4];
vertices[0][0] = coordinates[i];
vertices[1][0] = coordinates[i + 1];
vertices[0][1] = coordinates[i + 3];
vertices[1][1] = coordinates[i + 4];
vertices[0][2] = coordinates[i + 6];
vertices[1][2] = coordinates[i + 7];
vertices[0][3] = coordinates[i + 9];
vertices[1][3] = coordinates[i + 10];
// Must be clockwise (batik screws up otherwise)
int clockwise = clockwise(vertices[0], vertices[1]);
if (clockwise >= 0) {
vertices[0] = reverseDirection(vertices[0]);
vertices[1] = reverseDirection(vertices[1]);
}
fillShapeReprojected(
vertices, color, graphics, transformToScreenCoords);
baseColor += 4 * numRefColours;
}
}
/**
* Plot a PointArray into postscript format
*
* @param pointArray The pointArray to be plotted
* @param colours The colour to plot the points
* @param size Size of the points
* @param graphics The graphics to plot to
*/
private void plot(PointArray pointArray, Color[] colours, float size,
Graphics2D graphics) {
boolean hasAlpha = hasAlpha(pointArray);
boolean byRef = isByReference(pointArray);
byte[] refColours = null;
int numRefColours = (hasAlpha)
? 4
: 3;
int baseColor = 0;
float[] colour = new float[4];
if (colours != null) {
colour[0] = ((float)colours[0].getRed()) / 255.0f;
colour[1] = ((float)colours[0].getGreen()) / 255.0f;
colour[2] = ((float)colours[0].getBlue()) / 255.0f;
colour[3] = ((float)colours[0].getAlpha()) / 255.0f;
} else {
if (byRef) {
// DisplayImplJ3D stores ref as bytes
refColours = pointArray.getColorRefByte();
}
}
if (!hasAlpha || !useTransparency) {
colour[3] = 1.0f;
}
// If monochrome
if (monochrome) {
// Set everything except white to black
monochromatise(colour);
}
// Temporary variables for retrieving coordinates
int vertexCount = pointArray.getVertexCount();
int nCoords = 3 * vertexCount;
float[] coordinates = null;
// Get the coordinates
if (byRef) {
coordinates = pointArray.getCoordRefFloat();
}
else {
coordinates = new float[nCoords];
pointArray.getCoordinates(0, coordinates);
}
coordinates = transformToScreen(coordinates);
graphics.setStroke(getStroke(size));
float hsize = 0.5f * size;
// Loop over each point
for (int i = 0; i < vertexCount; i++) {
float x = coordinates[i * 3];
float y = coordinates[i * 3 + 1];
float[] vertices = {x, y};
if (!transformToScreenCoords) {
float[] device = new float[2];
viewPort.transform(new float[] {x, y}, 0, vertices, 0, 1);
}
if (colours == null) {
//pointArray.getColor(i, colour);
//triangleArray.getColor(cCount++, colour);
if (refColours != null) { // means we are BY_REFERENCE
for (int j = 0; j < numRefColours; j++) {
colour[j] = byteToFloat(refColours[i * numRefColours + j]);
}
}
else {
pointArray.getColor(i, colour);
}
}
if (!useTransparency || !hasAlpha) {
colour[3] = 1.0f;
}
// If monochrome
if (monochrome) {
// Set everything except white to black
monochromatise(colour);
}
Color color = new Color(colour[0], colour[1], colour[2], colour[3]);
graphics.setColor(color);
graphics.fill(new Rectangle2D.Float(x - hsize, y - hsize, size, size));
/*
graphics.setStroke(getStroke(1));
graphics.fillRect(
(int)vertices[0], (int)vertices[1], (int)size, (int)size);
*/
}
}
/**
* Get the number of colour components for the geometry array
* @param array the array to check
*
* @return true if vertexFormat has COLOR_4
*/
private boolean hasAlpha(GeometryArray array) {
int vertexFormat = array.getVertexFormat();
if ((vertexFormat & GeometryArray.COLOR_4) == GeometryArray.COLOR_4) {
return true;
}
return false;
}
/**
* Get whether this array uses BY_REFERENCE for it's coordinates and colors
* @param array the array to check
*
* @return true if vertexFormat has BY_REFERENCE
*/
private boolean isByReference(GeometryArray array) {
int vertexFormat = array.getVertexFormat();
if ((vertexFormat & GeometryArray.BY_REFERENCE) != 0) {
return true;
}
return false;
}
/**
* Convert an IndexedTriangleStripArray into plottable vectors
*
* @param triangleArray The IndexedTriangleStripArray
* @param colours The list of colors at each vertex
* @param thickness The line thickness
* @param graphics The graphics
*/
private void plot(IndexedTriangleStripArray triangleArray, Color[] colours,
float thickness, Graphics2D graphics) {}
/**
* Get the BasicStroke with the given thickness
*
* @param thickness the thickness
*
* @return the BasicStroke
*/
private BasicStroke getStroke(float thickness) {
BasicStroke stroke = new BasicStroke(thickness * (float)lineThickness,
BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER);
return stroke;
}
/**
* Get the BasicStroke with the given thickness and dash style
*
* @param thickness the line thickness
* @param dash the dash array
*
* @return the stroke
*/
private BasicStroke getStroke(float thickness, float[] dash) {
BasicStroke stroke = new BasicStroke(thickness * (float)lineThickness,
BasicStroke.CAP_ROUND,
BasicStroke.JOIN_ROUND, 1.0f, dash,
5.0f);
return stroke;
}
/**
* Make colours monochromatic by setting everything except white to
* black
*
* @param colour color to monochromatise
*/
private void monochromatise(float[] colour) {
if ((colour[0] < 0.8f) && (colour[1] < 0.8f) && (colour[2] < 0.8f)) {
colour[0] = 0.0f;
colour[1] = 0.0f;
colour[2] = 0.0f;
}
// Remove transparency
if (colour.length == 4) {
colour[3] = 1.0f;
}
}
/**
* Test the vertices to see if they form a clockwise or anticlockwise
* shape.
*
* @param xCoords the x coords
* @param yCoords the y coords
*
* @return 1 means clockwise, -1 means anticlockwise, 0 means
* indeterminate
*/
private int clockwise(float[] xCoords, float[] yCoords) {
int ccw = 0;
int numCoords = xCoords.length;
for (int i = 0; i < numCoords; i++) {
int j = (i + 1) % numCoords;
int k = (i + 2) % numCoords;
float crossProduct = (xCoords[j] - xCoords[i]) *
(yCoords[k] - yCoords[j]);
crossProduct -= (yCoords[j] - yCoords[i]) * (xCoords[k] - xCoords[j]);
if (crossProduct > 0) {
ccw++;
}
else if (crossProduct < 0) {
ccw--;
}
}
int clockwise = 0;
if (ccw > 0) {
clockwise = -1;
}
else if (ccw < 0) {
clockwise = 1;
}
return clockwise;
}
/**
* Test the vertices to see if they form a clockwise or anticlockwise
* shape.
* @param xCoords the x coords
* @param yCoords the y coords
*
* @return 1 means clockwise, -1 means anticlockwise, 0 means
* indeterminate
*/
private int clockwise(int[] xCoords, int[] yCoords) {
int ccw = 0;
int numCoords = xCoords.length;
for (int i = 0; i < numCoords; i++) {
int j = (i + 1) % numCoords;
int k = (i + 2) % numCoords;
float crossProduct = (xCoords[j] - xCoords[i]) *
(yCoords[k] - yCoords[j]);
crossProduct -= (yCoords[j] - yCoords[i]) * (xCoords[k] - xCoords[j]);
if (crossProduct > 0) {
ccw++;
}
else if (crossProduct < 0) {
ccw--;
}
}
int clockwise = 0;
if (ccw > 0) {
clockwise = -1;
}
else if (ccw < 0) {
clockwise = 1;
}
return clockwise;
}
/**
* Reverse the order of the vertices in a triangle from clockwise to
* anticlockwise and vice versa
*
* @param coords The x (or y) coordinates of the triangle
*
* @return the reversed coordinates
*/
private int[] reverseDirection(int[] coords) {
int temp = coords[2];
coords[2] = coords[1];
coords[1] = temp;
return coords;
}
/**
* Reverse the order of the vertices in a polygon from clockwise to
* anticlockwise and vice versa
*
* @param coords The x (or y) coordinates of the triangle
*
* @return the reversed coordinates
*/
private float[] reverseDirection(float[] coords) {
int numCoords = coords.length;
float[] temp = new float[numCoords];
for (int i = 0; i < numCoords; i++) {
temp[i] = coords[numCoords - i - 1];
}
return temp;
}
/**
* Long winded way of checking if any part of an area is visible in an
* area bounded by the graphics2d clipping region Significantly
* increases the plotting time, but significantly decreases the size of
* the resulting file Probably better to investigate ways of clipping
* the lines using DelaunayCustom.clip()
*
* @param x1 The X coordinate of the start of the line
* @param y1 The Y coordinate of the start of the line
* @param x2 The X coordinate of the end of the line
* @param y2 The Y coordinate of the end of the line
* @param graphics the graphics
* @return a boolean flag, true means that the triangle is at least
* partially visible
*
* @return true if visible
*/
private boolean visible(double x1, double y1, double x2, double y2,
Graphics2D graphics) {
return visible(x1, y1, x2, y2, graphics, false);
}
/**
* Long winded way of checking if any part of an area is visible in an
* area bounded by the graphics2d clipping region Significantly
* increases the plotting time, but significantly decreases the size of
* the resulting file Probably better to investigate ways of clipping
* the lines using DelaunayCustom.clip()
*
* @param x1 The X coordinate of the start of the line
* @param y1 The Y coordinate of the start of the line
* @param x2 The X coordinate of the end of the line
* @param y2 The Y coordinate of the end of the line
* @param graphics the graphics
* @param isScreen true to transform xy to graphics space
* @return a boolean flag, true means that the triangle is at least
* partially visible
*
* @return true if visible
*/
private boolean visible(double x1, double y1, double x2, double y2,
Graphics2D graphics, boolean isScreen) {
double[] coords = {x1, y1, x2, y2};
double[] device = new double[4];
if (!isScreen) {
viewPort.transform(coords, 0, device, 0, 2);
}
else {
device = coords;
}
// Find the area occupied by the line
double xMin = Math.min(device[0], device[2]);
double xMax = Math.max(device[0], device[2]);
double yMin = Math.min(device[1], device[3]);
double yMax = Math.max(device[1], device[3]);
int x = (int)xMin;
int y = (int)yMin;
// Must add 1 to these, because otherwise a vertical or
// horizontal line will have 0 area, and be treated as
// not intersecting, even when it does
int width = (int)(xMax - xMin) + 1;
int height = (int)(yMax - yMin) + 1;
// If any of this area intersects the clip, it is visible
boolean visible = graphics.hitClip(x, y, width, height);
return visible;
}
/**
* Is the Shape visible
*
* @param shape shape to check
*
* @return true if visible
*/
private boolean visible(Shape shape) {
boolean visible = shape.intersects(0, 0, width, height);
return visible;
}
/**
* Long winded way of checking if any part of an area is visible in an
* area bounded by the graphics2s clipping area Significantly increases
* the plotting time, but significantly decreases the size of the
* resulting file Probably better to investigate ways of clipping the
* lines using DelaunayCustom.clip()
*
* @param xCoords -
* The X coordinates of the area
* @param yCoords -
* The Y coordinates of the area
* @param graphics the graphics
* @return a boolean flag, true means that the triangle is at least
* partially visible
*/
private boolean visible(int[] xCoords, int[] yCoords, Graphics2D graphics) {
int numVertices = xCoords.length;
int xMin = Integer.MAX_VALUE;
int yMin = Integer.MAX_VALUE;
int xMax = Integer.MIN_VALUE;
int yMax = Integer.MIN_VALUE;
for (int i = 0; i < numVertices; i++) {
float[] display = {xCoords[i], yCoords[i]};
float[] device = new float[2];
// Convert the display coordinates to device coordinates
viewPort.transform(display, 0, device, 0, 2);
if (device[0] > xMax) {
xMax = (int)device[0];
}
if (device[0] < xMin) {
xMin = (int)device[0];
}
if (device[1] > yMax) {
yMax = (int)device[1];
}
if (device[1] < yMin) {
yMin = (int)device[1];
}
}
int x = (int)xMin;
int y = (int)yMin;
// Must add 1 to these, because otherwise a vertical or
// horizontal line will have 0 area, and be treated as
// not intersecting, even when it does
int width = (int)(xMax - xMin) + 1;
int height = (int)(yMax - yMin) + 1;
// If any of this area intersects the clip, it is visible
boolean visible = graphics.hitClip(x, y, width, height);
return visible;
}
/**
* Perform Sutherland-Hodgman Clipping on an array of vertices
* Not the most efficient method, but it works
* @param inVertexArray Array of vertices array[0] - x values,
* array[1] y values
* @return Clipped vertices
*/
private int[][] clip(int[][] inVertexArray) {
int[][] edge = new int[2][2];
// Clip Left Edge
edge[0][0] = 0;
edge[1][0] = height;
edge[0][1] = 0;
edge[1][1] = 0;
inVertexArray = SutherlandHodgmanPolygonClip(inVertexArray, edge);
// Clip Bottom Edge
edge[0][0] = 0;
edge[1][0] = 0;
edge[0][1] = width;
edge[1][1] = 0;
inVertexArray = SutherlandHodgmanPolygonClip(inVertexArray, edge);
// Clip Right Edge
edge[0][0] = width;
edge[1][0] = 0;
edge[0][1] = width;
edge[1][1] = height;
inVertexArray = SutherlandHodgmanPolygonClip(inVertexArray, edge);
// Clip Top Edge
edge[0][0] = width;
edge[1][0] = height;
edge[0][1] = width;
edge[1][1] = height;
inVertexArray = SutherlandHodgmanPolygonClip(inVertexArray, edge);
return inVertexArray;
}
/**
* Perform Sutherland-Hodgman Clipping on an array of vertices
* Not the most efficient method, but it works
* @param path GeneralPath to clip
*
* @return Clipped vertices
*/
private GeneralPath clip(GeneralPath path) {
float[][] edge = new float[2][2];
// Clip Left Edge
edge[0][0] = 0;
edge[1][0] = (height * 10);
edge[0][1] = 0;
edge[1][1] = -(height * 10);
path = SutherlandHodgmanPolygonClip(path, edge);
// Clip Bottom Edge
edge[0][0] = -(width * 10);
edge[1][0] = 0;
edge[0][1] = (width * 10);
edge[1][1] = 0;
path = SutherlandHodgmanPolygonClip(path, edge);
// Clip Right Edge
edge[0][0] = (width * 10);
edge[1][0] = -(height * 10);
edge[0][1] = (width * 10);
edge[1][1] = (height * 10);
path = SutherlandHodgmanPolygonClip(path, edge);
// Clip Top Edge
edge[0][0] = (width * 10);
edge[1][0] = (height * 10);
edge[0][1] = -(width * 10);
edge[1][1] = (height * 10);
path = SutherlandHodgmanPolygonClip(path, edge);
return path;
}
/**
* Perform Sutherland-Hodgman Clipping on an array of vertices
* Not the most efficient method, but it works
*
* @param path GeneralPath to clip
* @return Clipped vertices
*/
private GeneralPath clip2(GeneralPath path) {
float[][] edge = new float[2][2];
// Clip Left Edge
edge[0][0] = 0;
edge[1][0] = height;
edge[0][1] = 0;
edge[1][1] = 0;
path = SutherlandHodgmanPolygonClip(path, edge);
// Clip Bottom Edge
edge[0][0] = 0;
edge[1][0] = 0;
edge[0][1] = width;
edge[1][1] = 0;
path = SutherlandHodgmanPolygonClip(path, edge);
// Clip Right Edge
edge[0][0] = width;
edge[1][0] = 0;
edge[0][1] = width;
edge[1][1] = height;
path = SutherlandHodgmanPolygonClip(path, edge);
// Clip Top Edge
edge[0][0] = width;
edge[1][0] = height;
edge[0][1] = 0;
edge[1][1] = height;
path = SutherlandHodgmanPolygonClip(path, edge);
return path;
}
/**
* Perform Sutherland-Hodgman clipping against a single edge
* @param inVertexArray Array of vertices
* @param edge Edge definition - corners must be defined in
* anticlockwise order
* @return
*/
private int[][] SutherlandHodgmanPolygonClip(int[][] inVertexArray,
int[][] edge) {
int numVertices = inVertexArray[0].length;
int[][] outVertexArray = null;
if (numVertices <= 1) {
outVertexArray = new int[1][0];
return outVertexArray;
}
ArrayList xList = new ArrayList();
ArrayList yList = new ArrayList();
int xs = inVertexArray[0][inVertexArray[0].length - 1];
int ys = inVertexArray[1][inVertexArray[0].length - 1];
for (int j = 0; j < inVertexArray[0].length; j++) {
// Get the next point from the array
int xp = inVertexArray[0][j];
int yp = inVertexArray[1][j];
// If the next point is within the clip
if (insideEdge(xp, yp, edge)) {
// If previous point was also within the clip
if (insideEdge(xs, ys, edge)) {
xList.add(new Integer(xp));
yList.add(new Integer(yp));
}
else {
// Otherwise, intersect at the clip edge
int[] inter = intersect(new int[] {xs, ys}, new int[] {xp, yp},
edge);
xList.add(new Integer(inter[0]));
yList.add(new Integer(inter[1]));
xList.add(new Integer(xp));
yList.add(new Integer(yp));
}
}
else {
// Join the last point to the clip
if (insideEdge(xs, ys, edge)) {
int[] inter = intersect(new int[] {xs, ys}, new int[] {xp, yp},
edge);
xList.add(new Integer(inter[0]));
yList.add(new Integer(inter[1]));
}
}
xs = xp;
ys = yp;
}
outVertexArray = new int[2][xList.size()];
for (int i = 0; i < xList.size(); i++) {
Integer xInt = (Integer)xList.get(i);
Integer yInt = (Integer)yList.get(i);
outVertexArray[0][i] = xInt.intValue();
outVertexArray[1][i] = yInt.intValue();
}
return outVertexArray;
}
/**
* Perform Sutherland-Hodgman clipping against a single edge
*
* @param path The path
* @param edge Edge definition - corners must be defined in
* anticlockwise order
* @return
*/
private GeneralPath SutherlandHodgmanPolygonClip(GeneralPath path,
float[][] edge) {
ArrayList xList = new ArrayList();
ArrayList yList = new ArrayList();
AffineTransform blank = AffineTransform.getScaleInstance(1.0, 1.0);
java.awt.geom.PathIterator iterator = path.getPathIterator(blank);
float[] coords = new float[6];
iterator.currentSegment(coords);
float xs = coords[0];
float ys = coords[1];
iterator.next();
if (insideEdge(xs, ys, edge)) {
xList.add(new Float(xs));
yList.add(new Float(ys));
}
while(!iterator.isDone()) {
// Get the next point from the array
iterator.currentSegment(coords);
float xp = coords[0];
float yp = coords[1];
// If the next point is within the clip
if (insideEdge(xp, yp, edge)) {
// If previous point was also within the clip
if (insideEdge(xs, ys, edge)) {
xList.add(new Float(xp));
yList.add(new Float(yp));
}
else {
// Otherwise, intersect at the clip edge
float[] inter = intersect(new float[] {xs, ys}, new float[] {xp,
yp}, edge);
xList.add(new Float(inter[0]));
yList.add(new Float(inter[1]));
xList.add(new Float(xp));
yList.add(new Float(yp));
}
}
else {
// Join the last point to the clip
if (insideEdge(xs, ys, edge)) {
float[] inter = intersect(new float[] {xs, ys}, new float[] {xp,
yp}, edge);
xList.add(new Float(inter[0]));
yList.add(new Float(inter[1]));
}
}
xs = xp;
ys = yp;
iterator.next();
}
GeneralPath clippedPath = new GeneralPath();
if (xList.size() < 1) {
return clippedPath;
}
Float xInt = (Float)xList.get(0);
Float yInt = (Float)yList.get(0);
clippedPath.moveTo(xInt.floatValue(), yInt.floatValue());
for (int i = 1; i < xList.size(); i++) {
xInt = (Float)xList.get(i);
yInt = (Float)yList.get(i);
clippedPath.lineTo(xInt.floatValue(), yInt.floatValue());
}
return clippedPath;
}
/**
* Test if a point is "within" and edge of a rectangle
* @param x X coordinate of point
* @param y Y coordinate of point
* @param edge The edge to compare - must be defined in anticlockwise
* order
* @return True if the point is within the rectangle
*/
private boolean insideEdge(int x, int y, int[][] edge) {
final int X = 0;
final int Y = 1;
boolean inside = false;
// If bottom edge
if (edge[X][1] > edge[X][0]) {
if (y >= edge[Y][0]) {
inside = true;
}
}
// If Top Edge
if (edge[X][1] < edge[X][0]) {
if (y <= edge[Y][0]) {
inside = true;
}
}
// If Right Edge
if (edge[Y][1] > edge[Y][0]) {
if (x <= edge[X][1]) {
inside = true;
}
}
// If Left Edge
if (edge[Y][1] < edge[Y][0]) {
if (x >= edge[X][1]) {
inside = true;
}
}
return inside;
}
/**
* Test if a point is "within" and edge of a rectangle
* @param x X coordinate of point
* @param y Y coordinate of point
* @param edge The edge to compare - must be defined in anticlockwise
* order
* @return True if the point is within the rectangle
*/
private boolean insideEdge(float x, float y, float[][] edge) {
final int X = 0;
final int Y = 1;
boolean inside = false;
// If bottom edge
if (edge[X][1] > edge[X][0]) {
if (y >= edge[Y][0]) {
inside = true;
}
}
// If Top Edge
if (edge[X][1] < edge[X][0]) {
if (y <= edge[Y][0]) {
inside = true;
}
}
// If Right Edge
if (edge[Y][1] > edge[Y][0]) {
if (x <= edge[X][1]) {
inside = true;
}
}
// If Left Edge
if (edge[Y][1] < edge[Y][0]) {
if (x >= edge[X][1]) {
inside = true;
}
}
return inside;
}
/**
* Intersect a line between two vertices which crosses the edge of the
* clipping region
* @param first First point - int[0] is x value, int[1] is y value
* @param second Second point - int[0] is x value, int[1] is y value
* @param edge The edge definition - Must be defined in anticlockwise
* order
* @return
*/
private int[] intersect(int[] first, int[] second, int[][] edge) {
final int X = 0;
final int Y = 1;
int[] intersect = new int[2];
// If the edge is horizontal
if (edge[Y][0] == edge[Y][1]) {
intersect[Y] = edge[Y][0];
intersect[X] = first[X] +
(edge[Y][0] - first[Y]) * (second[X] - first[X]) /
(second[Y] - first[Y]);
}
else {
intersect[X] = edge[X][0];
intersect[Y] = first[Y] +
(edge[X][0] - first[X]) * (second[Y] - first[Y]) /
(second[X] - first[X]);
}
return intersect;
}
/**
* Intersect a line between two vertices which crosses the edge of the
* clipping region
* @param first First point - int[0] is x value, int[1] is y value
* @param second Second point - int[0] is x value, int[1] is y value
* @param edge The edge definition - Must be defined in anticlockwise
* order
* @return
*/
private float[] intersect(float[] first, float[] second, float[][] edge) {
final int X = 0;
final int Y = 1;
float[] intersect = new float[2];
// If the edge is horizontal
if (edge[Y][0] == edge[Y][1]) {
intersect[Y] = edge[Y][0];
intersect[X] = first[X] +
(edge[Y][0] - first[Y]) * (second[X] - first[X]) /
(second[Y] - first[Y]);
}
else {
intersect[X] = edge[X][0];
intersect[Y] = first[Y] +
(edge[X][0] - first[X]) * (second[Y] - first[Y]) /
(second[X] - first[X]);
}
return intersect;
}
/**
* Chart exception
*/
public class ChartException extends Exception {
/**
* Create a chart exception
*
* @param reason reason for the exception
*/
public ChartException(String reason) {
super(reason);
}
}
/**
* Provide a set of simple hatching patterns
*/
public class Hatching {
/** number of patterns */
public static final int NUM_PATTERNS = 6;
/** diagonal patter 1 */
public static final int DIAGONAL1 = 0;
/** diagonal pattern 2 */
public static final int DIAGONAL2 = 1;
/** diagonal both */
public static final int DIAGONAL_BOTH = 2;
/** horizontal pattern */
public static final int HORIZONTAL = 3;
/** vertical pattern */
public static final int VERTICAL = 4;
/** square pattern */
public static final int SQUARE = 5;
/** width */
private int width = 300;
/** height */
private int height = 300;
/**
* Get the pattern as an image
*
* @param pattern the pattern
*
* @return the image
*/
public BufferedImage getPattern(int pattern) {
// Create a 1 bit (monochrome) image
BufferedImage fillTexture = new BufferedImage(width, height,
BufferedImage.TYPE_INT_RGB);
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
setPoint(i, j, Color.WHITE, fillTexture);
}
}
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
if (isSet(pattern, i, j)) {
setPoint(i, j, Color.BLACK, fillTexture);
}
}
}
return fillTexture;
}
/**
* Test whether an x,y point for a given pattern is filled
*
* @param pattern The pattern
* @param x The x location of the point
* @param y The y location of the point
*
* @return true if filled
*/
private boolean isSet(int pattern, int x, int y) {
boolean isSet = false;
int repeat = 3;
if (pattern == DIAGONAL1) {
int xx = width / repeat;
int yy = height / repeat;
if ((x % xx) == (y % yy)) {
isSet = true;
}
}
else if (pattern == DIAGONAL2) {
int xx = width / repeat;
int yy = height / repeat;
if ((x % xx) == (yy - (y % yy))) {
isSet = true;
}
}
else if (pattern == DIAGONAL_BOTH) {
int xx = width / repeat;
int yy = height / repeat;
if (((x % xx) == (y % yy)) || ((x % xx) == (yy - (y % yy)))) {
isSet = true;
}
}
else if (pattern == HORIZONTAL) {
int yy = height / repeat;
if ((y % yy) == 0) {
isSet = true;
}
}
else if (pattern == VERTICAL) {
int xx = width / repeat;
if ((x % xx) == 0) {
isSet = true;
}
}
else if (pattern == SQUARE) {
int xx = width / repeat;
int yy = height / repeat;
if (((x % xx) == 0) || ((y % yy) == 0)) {
isSet = true;
}
}
return isSet;
}
/**
* Set a point in a buffered image. Actually, set a 3x3 square,
* it looks better.
*
* @param x
* The x location of the centre of the square
* @param y
* The y location of the centre of the square
* @param color color The colour of the square
* @param image The buffered image to be plotted
*/
private void setPoint(int x, int y, Color color, BufferedImage image) {
int rgb = color.getRGB();
for (int i = x - 1; i <= x + 1; i++) {
for (int j = y - 1; j <= y + 1; j++) {
if ((i >= 0) && (i < width) && (j >= 0) && (j < height)) {
image.setRGB(i, j, rgb);
}
}
}
}
}
/**
* Get the BasicStroke float arrays for dashing
* @param dashStyle dashing style
* @param pixelWidth
* @return array of alternating stroke lengths (on/off)
*/
private float[] getStrokeDash(int dashStyle, float pixelWidth) {
float[] dash = null;
switch (dashStyle) {
case LineAttributes.PATTERN_DOT:
// 1 on, 7 off
dash = new float[] {1.0f * pixelWidth, 7.0f * pixelWidth};
break;
case LineAttributes.PATTERN_DASH_DOT:
// 7 on, 4 off, 1 on, 4 off
dash = new float[] {7.0f * pixelWidth, 4.0f * pixelWidth,
1.0f * pixelWidth, 4 * pixelWidth};
break;
case LineAttributes.PATTERN_DASH: //
// 8 on, 8 off
dash = new float[] {8.0f * pixelWidth, 8.0f * pixelWidth};
break;
case LineAttributes.PATTERN_SOLID: //
default: //
break;
}
return dash;
}
/**
* Set whether the vertices should be transformed from display coords
* to screen coords before drawing. This ends up bypassing the use of
* the AffineTransform when drawing. Use this if you are in a rotated
* 3D display.
*
* @param value true to transform
*/
public void setTransformToScreenCoords(boolean value) {
transformToScreenCoords = value;
}
/**
* Transform coordinates from 3D display space to 2D space (AWT screen) space
* @param coords display space coords
*
* @return screen coords
*/
private float[] transformToScreen(float[] coords) {
if (!transformToScreenCoords) return coords;
int numvertex = coords.length / 3;
float[] retCoords = new float[coords.length];
for (int i = 0; i < numvertex; i++) {
double[] xyz = {coords[i * 3], coords[i * 3 + 1], coords[i * 3 + 2]};
int[] screenLocs = behavior.getScreenCoords(xyz);
retCoords[i * 3] = screenLocs[0];
retCoords[i * 3 + 1] = screenLocs[1];
retCoords[i * 3 + 2] = 0;
}
return retCoords;
}
}