package org.archstudio.bna.things.graphs;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.nio.FloatBuffer;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import javax.media.opengl.GL;
import javax.media.opengl.GL2ES2;
import javax.media.opengl.fixedfunc.GLMatrixFunc;
import org.archstudio.bna.IBNAView;
import org.archstudio.bna.ICoordinate;
import org.archstudio.bna.ICoordinateMapper;
import org.archstudio.bna.things.AbstractThingPeer;
import org.archstudio.bna.things.graphs.NumericAxis.Range;
import org.archstudio.bna.things.graphs.NumericAxis.Value;
import org.archstudio.bna.things.graphs.NumericSurfaceGraphThing.Data;
import org.archstudio.bna.ui.IUIResources.FontMetrics;
import org.archstudio.bna.ui.jogl.IJOGLResources;
import org.archstudio.bna.ui.swt.ISWTResources;
import org.archstudio.bna.utils.BNAUtils;
import org.archstudio.swtutils.constants.HorizontalAlignment;
import org.archstudio.swtutils.constants.LineStyle;
import org.archstudio.swtutils.constants.VerticalAlignment;
import org.archstudio.sysutils.ExpandableFloatBuffer;
import org.archstudio.sysutils.Matrix;
import org.archstudio.sysutils.SystemUtils;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import com.google.common.collect.Lists;
import com.jogamp.common.nio.Buffers;
import com.jogamp.opengl.util.PMVMatrix;
public class NumericSurfaceGraphThingPeer<T extends NumericSurfaceGraphThing> extends AbstractThingPeer<T> {
private static final double NUDGE = 0.75;
private static class Point {
float rgba[];
float xyz[];
public Point(RGB rgb, double x, double y, double z) {
rgba = new float[] { rgb.red / 255f, rgb.green / 255f, rgb.blue / 255f, 1 };
xyz = new float[] { (float) x, (float) y, (float) z };
}
}
private static class Line {
float[] xyz1;
float[] xyz2;
public Line(double x1, double y1, double z1, double x2, double y2, double z2) {
xyz1 = new float[] { (float) x1, (float) y1, (float) z1 };
xyz2 = new float[] { (float) x2, (float) y2, (float) z2 };
}
public Line(Point p1, Point p2) {
xyz1 = p1.xyz;
xyz2 = p2.xyz;
}
}
private static class Triangle {
Point[] p;
public Triangle(Point p1, Point p2, Point p3) {
p = new Point[] { p1, p2, p3 };
Arrays.sort(p, new Comparator<Point>() {
@Override
public int compare(Point o1, Point o2) {
return o1.xyz[2] < o2.xyz[2] ? -1 : o1.xyz[2] > o2.xyz[2] ? 1 : 0;
}
});
}
public void addContourLines(Collection<Line> lines, double[] zValues) {
int i = Arrays.binarySearch(zValues, p[0].xyz[2]);
i = i < 0 ? -i - 1 : i;
while (i < zValues.length && zValues[i] <= p[1].xyz[2]) {
double z = zValues[i];
double x2 = position(p[0].xyz[2], z, p[2].xyz[2], p[0].xyz[0], p[2].xyz[0]);
double x1 = position(p[0].xyz[2], z, p[1].xyz[2], p[0].xyz[0], p[1].xyz[0]);
double y2 = position(p[0].xyz[2], z, p[2].xyz[2], p[0].xyz[1], p[2].xyz[1]);
double y1 = position(p[0].xyz[2], z, p[1].xyz[2], p[0].xyz[1], p[1].xyz[1]);
lines.add(new Line(x1, y1, z, x2, y2, z));
i++;
}
while (i < zValues.length && zValues[i] <= p[2].xyz[2]) {
double z = zValues[i];
double x0 = position(p[0].xyz[2], z, p[2].xyz[2], p[0].xyz[0], p[2].xyz[0]);
double x1 = position(p[1].xyz[2], z, p[2].xyz[2], p[1].xyz[0], p[2].xyz[0]);
double y0 = position(p[0].xyz[2], z, p[2].xyz[2], p[0].xyz[1], p[2].xyz[1]);
double y1 = position(p[1].xyz[2], z, p[2].xyz[2], p[1].xyz[1], p[2].xyz[1]);
lines.add(new Line(x0, y0, z, x1, y1, z));
i++;
}
}
private double position(double z0, double z, double z1, double v0, double v1) {
return (z - z0) / (z1 - z0) * (v1 - v0) + v0;
}
}
private double[] toValues(NumericAxis zAxis, Range zRange) {
List<NumericAxis.Value> zValuesList = Lists.newArrayList(zAxis.getAxisValues(zRange));
double[] zValues = new double[zValuesList.size()];
for (int i = 0; i < zValuesList.size(); i++) {
zValues[i] = zValuesList.get(i).linearOffset;
}
return zValues;
}
private Point2D.Double transformXY(Matrix matrix, double x, double y, double z) {
Matrix xy = matrix.product(Matrix.newRowMajorMatrix(1, x, y, z, 1));
return new Point2D.Double(xy.get(0, 0), xy.get(0, 1));
}
List<Object> cacheValidConditions = Lists.newArrayList();
Data data;
Matrix matrix;
double xyScale, zScale;
Range xRange, yRange, zRange;
double originOffsetX, originOffsetY;
boolean xCrossMin;
boolean yCrossMin;
boolean zCrossMin;
double xCrossOffset, yCrossOffset, zCrossOffset;
double xLabelCrossOffset, yLabelCrossOffset, zLabelCrossOffset, zxLabelCrossOffset, zyLabelCrossOffset;
HorizontalAlignment xLabelHorizontalAlignment, yLabelHorizontalAlignment;
VerticalAlignment xLabelVerticalAlignment, yLabelVerticalAlignment;
ExpandableFloatBuffer triangleVertices = new ExpandableFloatBuffer();
ExpandableFloatBuffer triangleColors = new ExpandableFloatBuffer();
NumericAxis zMajorAxis;
NumericAxis zMinorAxis;
double gridLinesAlpha;
ExpandableFloatBuffer gridVertices = new ExpandableFloatBuffer();
ExpandableFloatBuffer gridColors = new ExpandableFloatBuffer();
double minorContourAlpha;
ExpandableFloatBuffer minorContourVertices = new ExpandableFloatBuffer();
ExpandableFloatBuffer minorContourColors = new ExpandableFloatBuffer();
double majorContourAlpha;
ExpandableFloatBuffer majorContourVertices = new ExpandableFloatBuffer();
ExpandableFloatBuffer majorContourColors = new ExpandableFloatBuffer();
public NumericSurfaceGraphThingPeer(T thing, IBNAView view, ICoordinateMapper cm) {
super(thing, view, cm);
}
@Override
public void draw(GC gc, Rectangle localBounds, ISWTResources r) {
Rectangle lbb = cm.worldToLocal(t.getBoundingBox());
if (!lbb.intersects(localBounds)) {
return;
}
String message = "Unsupported UI";
Font f = new Font(Font.DIALOG, Font.PLAIN, 12);
Dimension d = r.getTextSize(f, message);
r.drawText(f, message, lbb.x + (lbb.width - d.width) / 2, lbb.y + (lbb.height - d.height) / 2,
new RGB(0, 0, 0), 1);
}
@Override
public void draw(GL2ES2 gl, Rectangle localBounds, IJOGLResources r) {
Rectangle lbb = cm.worldToLocal(t.getBoundingBox());
if (!lbb.intersects(localBounds)) {
return;
}
PMVMatrix glMatrix = r.getMatrix();
Data thingData = t.getData();
boolean flipData = false;
int xRotation = SystemUtils.loop(0, t.getXRotation(), 360);
int yRotation = SystemUtils.loop(0, t.getYRotation(), 360);
NumericAxis xMajorAxis = !flipData ? t.getXMajorAxis() : t.getYMajorAxis();
NumericAxis xMinorAxis = !flipData ? t.getXMinorAxis() : t.getYMinorAxis();
NumericAxis yMajorAxis = !flipData ? t.getYMajorAxis() : t.getXMajorAxis();
NumericAxis yMinorAxis = !flipData ? t.getYMinorAxis() : t.getXMinorAxis();
zMajorAxis = t.getZMajorAxis();
zMinorAxis = t.getZMinorAxis();
gridLinesAlpha = t.getGridAlpha();
minorContourAlpha = t.getMinorContourAlpha();
majorContourAlpha = t.getMajorContourAlpha();
updateMinorAxis(xMajorAxis, xMinorAxis);
updateMinorAxis(yMajorAxis, yMinorAxis);
updateMinorAxis(zMajorAxis, zMinorAxis);
List<Object> cacheValidConditions = Lists.newArrayList();
cacheValidConditions.add(thingData);
cacheValidConditions.add(flipData);
cacheValidConditions.add(xRotation);
cacheValidConditions.add(yRotation);
cacheValidConditions.add(xMajorAxis);
cacheValidConditions.add(xMinorAxis);
cacheValidConditions.add(yMajorAxis);
cacheValidConditions.add(yMinorAxis);
cacheValidConditions.add(zMajorAxis);
cacheValidConditions.add(zMinorAxis);
cacheValidConditions.add(gridLinesAlpha > 0);
cacheValidConditions.add(minorContourAlpha > 0);
cacheValidConditions.add(majorContourAlpha > 0);
// don't use the local bounding box as it can vary slightly depending on location
cacheValidConditions.add(BNAUtils.toDimension(t.getBoundingBox()));
cacheValidConditions.add(cm.getLocalScale());
if (!this.cacheValidConditions.equals(cacheValidConditions)) {
data = thingData;
if (flipData) {
data.flipXY();
}
// determine data range
double dataMin = Double.POSITIVE_INFINITY;
double dataMax = Double.NEGATIVE_INFINITY;
for (int y = 0; y < data.getHeight(); y++) {
for (int x = 0; x < data.getWidth(); x++) {
double value = data.getValue(x, y);
if (!Double.isNaN(value)) {
dataMin = Math.min(dataMin, value);
dataMax = Math.max(dataMax, value);
}
}
}
// determine axis ranges
xRange = xMajorAxis.getAxisRange(0, data.getWidth() - 1);
double xRangeMinOffset = xMajorAxis.toValue(xRange.min, xRange).linearOffset;
double xRangeMaxOffset = xMajorAxis.toValue(xRange.max, xRange).linearOffset;
yRange = yMajorAxis.getAxisRange(0, data.getHeight() - 1);
double yRangeMinOffset = yMajorAxis.toValue(yRange.min, yRange).linearOffset;
double yRangeMaxOffset = yMajorAxis.toValue(yRange.max, yRange).linearOffset;
zRange = zMajorAxis.getAxisRange(dataMin, dataMax);
double zRangeMinOffset = zMajorAxis.toValue(zRange.min, zRange).linearOffset;
double zRangeMaxOffset = zMajorAxis.toValue(zRange.max, zRange).linearOffset;
// determine graph bounds
double zMinXMin = Double.POSITIVE_INFINITY;
double zMinYMin = Double.POSITIVE_INFINITY;
double zMinXMax = Double.NEGATIVE_INFINITY;
double zMinYMax = Double.NEGATIVE_INFINITY;
double zMaxXMin = Double.POSITIVE_INFINITY;
double zMaxYMin = Double.POSITIVE_INFINITY;
double zMaxXMax = Double.NEGATIVE_INFINITY;
double zMaxYMax = Double.NEGATIVE_INFINITY;
glMatrix.glMatrixMode(GLMatrixFunc.GL_PROJECTION);
r.pushMatrix(GLMatrixFunc.GL_PROJECTION);
try {
glMatrix.glLoadIdentity();
glMatrix.glRotatef(90 - yRotation, 1, 0, 0);
glMatrix.glRotatef(xRotation, 0, 0, 1);
float[] f = new float[16];
glMatrix.glGetFloatv(GLMatrixFunc.GL_PROJECTION_MATRIX, f, 0);
matrix = Matrix.newColumnMajorMatrix(4, f);
}
finally {
r.popMatrix(GLMatrixFunc.GL_PROJECTION);
}
for (double x : new double[] { xRangeMinOffset, xRangeMaxOffset }) {
for (double y : new double[] { yRangeMinOffset, yRangeMaxOffset }) {
Point2D.Double p = transformXY(matrix, x, y, zRangeMinOffset);
zMinXMin = Math.min(zMinXMin, p.x);
zMinYMin = Math.min(zMinYMin, p.y);
zMinXMax = Math.max(zMinXMax, p.x);
zMinYMax = Math.max(zMinYMax, p.y);
}
}
for (double x : new double[] { xRangeMinOffset, xRangeMaxOffset }) {
for (double y : new double[] { yRangeMinOffset, yRangeMaxOffset }) {
Point2D.Double p = transformXY(matrix, x, y, zRangeMaxOffset);
zMaxXMin = Math.min(zMaxXMin, p.x);
zMaxYMin = Math.min(zMaxYMin, p.y);
zMaxXMax = Math.max(zMaxXMax, p.x);
zMaxYMax = Math.max(zMaxYMax, p.y);
}
}
// scale graph
double zMinXScale = lbb.width / Math.abs(zMinXMax - zMinXMin);
double zMaxXScale = lbb.width / Math.abs(zMaxXMax - zMaxXMin);
double xScale = Math.min(zMinXScale, zMaxXScale);
double zMinYScale = lbb.height / Math.abs(zMinYMax - zMinYMin);
double zMaxYScale = lbb.height / Math.abs(zMaxYMax - zMaxYMin);
double yScale = Math.min(zMinYScale, zMaxYScale);
xyScale = Math.min(xScale, yScale);
double zMaxScale = (lbb.height - xyScale * Math.abs(zMinYMax - zMinYMin)) / Math.abs(zMaxYMax - zMinYMax);
double zMinScale = (lbb.height - xyScale * Math.abs(zMinYMax - zMinYMin)) / Math.abs(zMaxYMin - zMinYMin);
zScale = Math.min(zMaxScale, zMinScale);
// determine offset
originOffsetX = -zMinXMin * xyScale + (lbb.width - xyScale * Math.abs(zMinXMax - zMinXMin)) / 2;
originOffsetY = lbb.height - Math.max(zMinYMax, zMaxYMax) * xyScale;
// determine cross points
xCrossMin = true;
yCrossMin = true;
zCrossMin = true;
boolean xLabelCrossMin = true;
boolean yLabelCrossMin = true;
boolean zLabelCrossMin = true;
boolean zxLabelCrossMin = true;
boolean zyLabelCrossMin = true;
if (xRotation > 90 && xRotation <= 270) {
yCrossMin ^= true;
zxLabelCrossMin ^= true;
}
else {
yLabelCrossMin ^= true;
}
if (xRotation > 180 && xRotation < 360 || xRotation == 0) {
xCrossMin ^= true;
}
else {
zyLabelCrossMin ^= true;
xLabelCrossMin ^= true;
}
xCrossMin ^= xMajorAxis.reverse;
yCrossMin ^= yMajorAxis.reverse;
zCrossMin ^= zMajorAxis.reverse;
xLabelCrossMin ^= xMajorAxis.reverse;
yLabelCrossMin ^= yMajorAxis.reverse;
zLabelCrossMin ^= zMajorAxis.reverse;
zxLabelCrossMin ^= xMajorAxis.reverse;
zyLabelCrossMin ^= yMajorAxis.reverse;
xCrossOffset = xMajorAxis.toValue(xCrossMin ? xRange.min : xRange.max, xRange).linearOffset;
yCrossOffset = yMajorAxis.toValue(yCrossMin ? yRange.min : yRange.max, yRange).linearOffset;
zCrossOffset = zMajorAxis.toValue(zCrossMin ? zRange.min : zRange.max, zRange).linearOffset;
xLabelCrossOffset = xMajorAxis.toValue(xLabelCrossMin ? xRange.min : xRange.max, xRange).linearOffset;
yLabelCrossOffset = yMajorAxis.toValue(yLabelCrossMin ? yRange.min : yRange.max, yRange).linearOffset;
zLabelCrossOffset = zMajorAxis.toValue(zLabelCrossMin ? zRange.min : zRange.max, zRange).linearOffset;
zxLabelCrossOffset = xMajorAxis.toValue(zxLabelCrossMin ? xRange.min : xRange.max, xRange).linearOffset;
zyLabelCrossOffset = yMajorAxis.toValue(zyLabelCrossMin ? yRange.min : yRange.max, yRange).linearOffset;
// calculate alignment of text labels
xLabelHorizontalAlignment = HorizontalAlignment.CENTER;
xLabelVerticalAlignment = VerticalAlignment.BOTTOM;
yLabelHorizontalAlignment = HorizontalAlignment.LEFT;
yLabelVerticalAlignment = VerticalAlignment.MIDDLE;
int xAlignmentRotation = SystemUtils.loop(0, xRotation, 180);
if (xAlignmentRotation > 0 && xAlignmentRotation <= 45) {
yLabelHorizontalAlignment = HorizontalAlignment.RIGHT;
yLabelVerticalAlignment = VerticalAlignment.MIDDLE;
}
if (xAlignmentRotation > 45 && xAlignmentRotation <= 90) {
xLabelHorizontalAlignment = HorizontalAlignment.LEFT;
xLabelVerticalAlignment = VerticalAlignment.MIDDLE;
yLabelHorizontalAlignment = HorizontalAlignment.CENTER;
yLabelVerticalAlignment = VerticalAlignment.BOTTOM;
}
if (xAlignmentRotation > 90 && xAlignmentRotation < 135) {
xLabelHorizontalAlignment = HorizontalAlignment.RIGHT;
xLabelVerticalAlignment = VerticalAlignment.MIDDLE;
yLabelHorizontalAlignment = HorizontalAlignment.CENTER;
yLabelVerticalAlignment = VerticalAlignment.BOTTOM;
}
// calculate offsets for each data value
int width = data.getWidth();
int height = data.getHeight();
double[] offsets = new double[width * height];
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
double value = data.getValue(x, y);
if (Double.isNaN(value)) {
offsets[y * width + x] = Double.NaN;
}
else {
offsets[y * width + x] = zMajorAxis.toValue(value, zRange).linearOffset;
}
}
}
// calculate final matrix
r.pushMatrix(GLMatrixFunc.GL_PROJECTION);
try {
glMatrix.glMatrixMode(GLMatrixFunc.GL_PROJECTION);
glMatrix.glLoadIdentity();
glMatrix.glRotatef(90 - yRotation, 1, 0, 0);
glMatrix.glRotatef(xRotation, 0, 0, 1);
glMatrix.glScalef((float) xyScale, (float) xyScale, (float) zScale);
float[] f = new float[16];
glMatrix.glGetFloatv(GLMatrixFunc.GL_PROJECTION_MATRIX, f, 0);
matrix = Matrix.newColumnMajorMatrix(4, f);
}
finally {
r.popMatrix(GLMatrixFunc.GL_PROJECTION);
}
// calculate points
Point[] points = new Point[width * height];
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int index = y * width + x;
if (!Double.isNaN(offsets[index])) {
points[index] = new Point(data.getColor(x, y), x, y, offsets[index]);
}
}
}
// calculate graph triangles, grid lines
List<Triangle> triangles = Lists.newArrayList();
List<Line> gridLines = Lists.newArrayList();
for (int y = 1; y < height; y++) {
X: for (int x = 1; x < width; x++) {
Point p1 = points[(y - 1) * width + x - 1];
Point p2 = points[(y - 1) * width + x - 0];
Point p3 = points[(y - 0) * width + x - 1];
Point p4 = points[(y - 0) * width + x - 0];
// if one value is NaN, merge it with an adjacent value to render a triangle
int totalNaN = 0;
int x1 = x - 1;
int x2 = x - 0;
int x3 = x - 1;
int x4 = x - 1;
if (p1 == null) {
totalNaN++;
p1 = p2;
x1 = x2;
}
if (p2 == null) {
totalNaN++;
p2 = p1;
x2 = x1;
}
if (p3 == null) {
totalNaN++;
p3 = p4;
x3 = x4;
}
if (p4 == null) {
totalNaN++;
p4 = p3;
x4 = x3;
}
// if more than one value is NaN, skip this quad
if (totalNaN > 1) {
continue X;
}
if (p1.xyz[2] + p4.xyz[2] > p2.xyz[2] + p3.xyz[2]) {
if (p3 != p4) {
triangles.add(new Triangle(p1, p3, p4));
}
if (p1 != p2) {
triangles.add(new Triangle(p1, p2, p4));
}
}
else {
if (p1 != p2) {
triangles.add(new Triangle(p1, p2, p3));
}
if (p3 != p4) {
triangles.add(new Triangle(p2, p3, p4));
}
}
if (gridLinesAlpha > 0) {
if (x == 1) {
gridLines.add(new Line(p1, p3));
}
gridLines.add(new Line(p2, p4));
if (y == 1) {
gridLines.add(new Line(p1, p2));
}
gridLines.add(new Line(p3, p4));
}
}
}
triangleVertices.rewind();
triangleColors.rewind();
for (Triangle t : triangles) {
for (Point p : t.p) {
triangleVertices.put(p.xyz);
triangleColors.put(p.rgba);
}
}
trim(triangleVertices, triangleColors);
gridVertices.rewind();
gridColors.rewind();
putAllLines(gridLines, new RGB(0, 0, 0), gridVertices, gridColors);
trim(gridVertices, gridColors);
// calculate contour lines
List<Line> minorContourLines = Lists.newArrayList();
if (minorContourAlpha > 0) {
double[] zValues = toValues(zMinorAxis, zRange);
for (Triangle t : triangles) {
t.addContourLines(minorContourLines, zValues);
}
}
minorContourVertices.rewind();
minorContourColors.rewind();
putAllLines(minorContourLines, new RGB(0, 0, 0), minorContourVertices, minorContourColors);
trim(minorContourVertices, minorContourColors);
List<Line> majorContourLines = Lists.newArrayList();
if (majorContourAlpha > 0) {
double[] zValues = toValues(zMajorAxis, zRange);
for (Triangle triangle : triangles) {
triangle.addContourLines(majorContourLines, zValues);
}
}
majorContourVertices.rewind();
majorContourColors.rewind();
putAllLines(majorContourLines, new RGB(0, 0, 0), majorContourVertices, majorContourColors);
trim(majorContourVertices, majorContourColors);
this.cacheValidConditions = cacheValidConditions;
}
// render graph
r.pushMatrix(GLMatrixFunc.GL_PROJECTION);
try {
gl.glEnable(GL.GL_DEPTH_TEST);
gl.glDepthFunc(GL.GL_LEQUAL);
glMatrix.glMatrixMode(GLMatrixFunc.GL_PROJECTION);
glMatrix.glTranslatef(lbb.x + (float) originOffsetX, lbb.y + (float) originOffsetY, 0);
glMatrix.glRotatef(90 - yRotation, 1, 0, 0);
glMatrix.glRotatef(xRotation, 0, 0, 1);
glMatrix.glScalef((float) xyScale, (float) xyScale, (float) zScale);
gl.glDepthRange(-10000, 10000);
drawGraph(gl, localBounds, r, data, xMajorAxis, yMajorAxis, zMajorAxis);
drawAxis(gl, localBounds, r, data, xMajorAxis, xMinorAxis, yMajorAxis, yMinorAxis, zMajorAxis, zMinorAxis);
}
finally {
r.popMatrix(GLMatrixFunc.GL_PROJECTION);
gl.glDisable(GL.GL_DEPTH_TEST);
}
// render labels
Font font = new Font(Font.DIALOG, Font.PLAIN, 12);
int tick = r.getTextSize(font, " ").width;
FontMetrics metrics = r.getFontMetrics(font);
for (Value v : xMajorAxis.getAxisValues(xRange)) {
String label = xMajorAxis.formatter.apply(v.actualValue);
Dimension size = r.getTextSize(font, label);
Point2D.Double p = transformXY(matrix, v.linearOffset, yLabelCrossOffset, zLabelCrossOffset);
p.x += lbb.x + originOffsetX;
p.y += lbb.y + originOffsetY;
double x = p.x;
double y = p.y;
switch (xLabelHorizontalAlignment) {
case LEFT:
x -= size.width + tick + metrics.getLeading();
r.drawShape(new Line2D.Double(p.x, p.y, p.x - tick, p.y), new RGB(0, 0, 0), 1, LineStyle.SOLID, 1);
break;
case CENTER:
x -= size.width / 2;
r.drawShape(new Line2D.Double(p.x, p.y, p.x, p.y + tick), new RGB(0, 0, 0), 1, LineStyle.SOLID, 1);
break;
case RIGHT:
x += tick + metrics.getLeading();
r.drawShape(new Line2D.Double(p.x, p.y, p.x + tick, p.y), new RGB(0, 0, 0), 1, LineStyle.SOLID, 1);
break;
}
switch (xLabelVerticalAlignment) {
case TOP:
y -= size.height;
break;
case MIDDLE:
y -= metrics.getLeading() + metrics.getAscent() / 2;
break;
case BOTTOM:
y += tick;
break;
}
r.drawText(font, label, x, y, new RGB(0, 0, 0), 1);
}
for (Value v : yMajorAxis.getAxisValues(yRange)) {
String label = yMajorAxis.formatter.apply(v.actualValue);
Dimension size = r.getTextSize(font, label);
Point2D.Double p = transformXY(matrix, xLabelCrossOffset, v.linearOffset, zLabelCrossOffset);
p.x += lbb.x + originOffsetX;
p.y += lbb.y + originOffsetY;
double x = p.x;
double y = p.y;
switch (yLabelHorizontalAlignment) {
case LEFT:
x -= size.width + tick + metrics.getLeading();
r.drawShape(new Line2D.Double(p.x, p.y, p.x - tick, p.y), new RGB(0, 0, 0), 1, LineStyle.SOLID, 1);
break;
case CENTER:
x -= size.width / 2;
r.drawShape(new Line2D.Double(p.x, p.y, p.x, p.y + tick), new RGB(0, 0, 0), 1, LineStyle.SOLID, 1);
break;
case RIGHT:
x += tick + metrics.getLeading();
r.drawShape(new Line2D.Double(p.x, p.y, p.x + tick, p.y), new RGB(0, 0, 0), 1, LineStyle.SOLID, 1);
break;
}
switch (yLabelVerticalAlignment) {
case TOP:
y -= size.height;
break;
case MIDDLE:
y -= metrics.getLeading() + metrics.getAscent() / 2;
break;
case BOTTOM:
y += tick;
break;
}
r.drawText(font, label, x, y, new RGB(0, 0, 0), 1);
}
for (Value v : zMajorAxis.getAxisValues(zRange)) {
String label = zMajorAxis.formatter.apply(v.actualValue);
Dimension size = r.getTextSize(font, label);
Point2D.Double p = transformXY(matrix, zxLabelCrossOffset, zyLabelCrossOffset, v.linearOffset);
p.x += lbb.x + originOffsetX;
p.y += lbb.y + originOffsetY;
r.drawShape(new Line2D.Double(p.x, p.y, p.x - tick, p.y), new RGB(0, 0, 0), 1, LineStyle.SOLID, 1);
double x = p.x - tick - size.width - metrics.getLeading();
double y = p.y - metrics.getLeading() - metrics.getAscent() / 2;
r.drawText(font, label, x, y, new RGB(0, 0, 0), 1);
}
if (t.isSelected()) {
r.selectShape(new Rectangle2D.Double(lbb.x, lbb.y, lbb.width, lbb.height), t.getRotatingOffset());
}
}
private void putAllLines(List<Line> gridLines, RGB rgb, ExpandableFloatBuffer vertices, ExpandableFloatBuffer colors) {
float[] rgbargba = new float[] {//
//
rgb.red / 255f, rgb.green / 255f, rgb.blue / 255f, 1,//
rgb.red / 255f, rgb.green / 255f, rgb.blue / 255f, 1 };
for (Line l : gridLines) {
vertices.put(l.xyz1);
vertices.put(l.xyz2);
colors.put(rgbargba);
}
}
private void trim(ExpandableFloatBuffer... buffers) {
for (ExpandableFloatBuffer buffer : buffers) {
buffer.trim();
}
}
private void updateMinorAxis(NumericAxis majorAxis, NumericAxis minorAxis) {
minorAxis.reverse = majorAxis.reverse;
}
private void drawGraph(GL2ES2 gl, Rectangle localBounds, IJOGLResources r, Data data, NumericAxis xAxis,
NumericAxis yAxis, NumericAxis zAxis) {
PMVMatrix glMatrix = r.getMatrix();
// render triangles
{
triangleVertices.rewind();
triangleColors.rewind();
r.fillShape(GL.GL_TRIANGLES, triangleVertices.getBackingBuffer(), triangleColors.getBackingBuffer(),
triangleVertices.limit() / 3);
}
// render contours
{
double zNudge = NUDGE / zScale;
double xyNudge = NUDGE / xyScale;
r.pushMatrix(GLMatrixFunc.GL_PROJECTION);
try {
glMatrix.glMatrixMode(GLMatrixFunc.GL_PROJECTION);
for (double nudge : new double[] { -1, 2 }) {
glMatrix.glTranslatef((float) (nudge * (xCrossMin ? -xyNudge : xyNudge)),
(float) (nudge * (yCrossMin ? -xyNudge : xyNudge)), (float) (nudge * (zCrossMin ? -zNudge
: zNudge)));
if (gridLinesAlpha > 0) {
drawLines(localBounds, r, gridVertices, gridColors, new RGB(0, 0, 0), LineStyle.SOLID,
gridLinesAlpha);
}
if (minorContourAlpha > 0) {
drawLines(localBounds, r, minorContourVertices, minorContourColors, new RGB(0, 0, 0),
zMinorAxis.lineStyle, minorContourAlpha);
}
if (majorContourAlpha > 0) {
drawLines(localBounds, r, majorContourVertices, majorContourColors, new RGB(0, 0, 0),
zMajorAxis.lineStyle, majorContourAlpha);
}
}
}
finally {
r.popMatrix(GLMatrixFunc.GL_PROJECTION);
}
}
}
private void drawLines(Rectangle localBounds, IJOGLResources r, ExpandableFloatBuffer vertices,
ExpandableFloatBuffer colors, RGB rgb, LineStyle lineStyle, double alpha) {
vertices.rewind();
colors.rewind();
r.drawShape(GL.GL_LINES, vertices.getBackingBuffer(), colors.getBackingBuffer(), vertices.limit() / 3,
lineStyle.toBitPattern());
}
private void drawAxis(GL2ES2 gl, Rectangle localBounds, IJOGLResources r, Data data, NumericAxis xMajorAxis,
NumericAxis xMinorAxis, NumericAxis yMajorAxis, NumericAxis yMinorAxis, NumericAxis zMajorAxis,
NumericAxis zMinorAxis) {
PMVMatrix glMatrix = r.getMatrix();
double xMin = xMajorAxis.toValue(xRange.min, xRange).linearOffset;
double xMax = xMajorAxis.toValue(xRange.max, xRange).linearOffset;
double yMin = yMajorAxis.toValue(yRange.min, yRange).linearOffset;
double yMax = yMajorAxis.toValue(yRange.max, yRange).linearOffset;
double zMin = zMajorAxis.toValue(zRange.min, zRange).linearOffset;
double zMax = zMajorAxis.toValue(zRange.max, zRange).linearOffset;
r.pushMatrix(GLMatrixFunc.GL_PROJECTION);
try {
double zNudge = NUDGE / zScale;
double xyNudge = NUDGE / xyScale;
glMatrix.glTranslatef((float) (xCrossMin ? -xyNudge : xyNudge), (float) (yCrossMin ? -xyNudge : xyNudge),
(float) (zCrossMin ? -zNudge : zNudge));
FloatBuffer colors = Buffers.newDirectFloatBuffer(new float[] { 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0,
0, 1 });
xMinorAxis.unit = 1;
xMinorAxis.lineStyle = LineStyle.DOT;
for (NumericAxis axis : Lists.newArrayList(xMinorAxis, xMajorAxis)) {
for (Value v : axis.getAxisValues(xRange)) {
r.drawShape(GL.GL_LINES, Buffers.newDirectFloatBuffer(new float[] { //
//
(float) v.linearOffset, (float) yCrossOffset, (float) zMin, //
(float) v.linearOffset, (float) yCrossOffset, (float) zMax, //
(float) v.linearOffset, (float) yMin, (float) zCrossOffset, //
(float) v.linearOffset, (float) yMax, (float) zCrossOffset //
}), colors, 4, axis.lineStyle.toBitPattern());
}
}
for (NumericAxis axis : Lists.newArrayList(yMinorAxis, yMajorAxis)) {
for (Value v : axis.getAxisValues(yRange)) {
r.drawShape(GL.GL_LINES, Buffers.newDirectFloatBuffer(new float[] { //
//
(float) xMin, (float) v.linearOffset, (float) zCrossOffset, //
(float) xMax, (float) v.linearOffset, (float) zCrossOffset, //
(float) xCrossOffset, (float) v.linearOffset, (float) zMin, //
(float) xCrossOffset, (float) v.linearOffset, (float) zMax //
}), colors, 4, axis.lineStyle.toBitPattern());
}
}
for (NumericAxis axis : Lists.newArrayList(zMinorAxis, zMajorAxis)) {
for (Value v : axis.getAxisValues(zRange)) {
r.drawShape(GL.GL_LINES, Buffers.newDirectFloatBuffer(new float[] { //
//
(float) xMin, (float) yCrossOffset, (float) v.linearOffset, //
(float) xMax, (float) yCrossOffset, (float) v.linearOffset, //
(float) xCrossOffset, (float) yMin, (float) v.linearOffset, //
(float) xCrossOffset, (float) yMax, (float) v.linearOffset //
}), colors, 4, axis.lineStyle.toBitPattern());
}
}
}
finally {
r.popMatrix(GLMatrixFunc.GL_PROJECTION);
}
}
@Override
public boolean isInThing(ICoordinate location) {
return t.getBoundingBox().contains(location.getWorldPoint());
}
}