/*
Copyright (C) 2001, 2006, 2007 United States Government
as represented by the Administrator of the
National Aeronautics and Space Administration.
All Rights Reserved.
*/
package gov.nasa.worldwind.render;
import javax.media.opengl.GL;
import javax.media.opengl.glu.GLU;
import java.awt.*;
import java.nio.DoubleBuffer;
import com.sun.opengl.util.BufferUtil;
import gov.nasa.worldwind.util.Logging;
/**
* Static class for drawing 2D frames.
* <p>
* All shapes are drawn inside a bounding rectangle whose lower left corner
* is at the origin. Shapes with a leader use an offset point that indicate where the
* leader triangle should point at - it usually has a negative y since the leader connects
* at the bottom of the frame (at y = 0).
* </p>
* @author Patrick Murris
* @version $Id: FrameFactory.java 5178 2008-04-25 21:51:20Z patrickmurris $
* @see AbstractAnnotation
*/
public class FrameFactory {
public static final String SHAPE_RECTANGLE = "Render.FrameFactory.ShapeRectangle";
public static final String SHAPE_ELLIPSE = "Render.FrameFactory.ShapeEllipse";
public static final String SHAPE_NONE = "Render.FrameFactory.ShapeNone";
public static final String LEADER_TRIANGLE = "Render.FrameFactory.LeaderTriangle";
public static final String LEADER_NONE = "Render.FrameFactory.LeaderNone";
private static int cornerSteps = 5;
private static int leaderGapWidth = 6;
private static int circleSteps = 36;
/**
* Draw a shape with the specified width and height, gl mode and corner radius. GL mode came be one of
* <code>GL.GL_TRIANGLE_FAN</code> and <code>GL.LINE_STRIP</code>. Corner radius only apply
* to <code>SHAPE_RECTANGLE</code> - set to zero for square corners.
* @param dc the current <code>DrawContext</code>.
* @param shape the shape - can be one of <code>SHAPE_RECTANGLE</code> or <code>SHAPE_ELLIPSE</code>.
* @param width the width of the overall shape.
* @param height the height of the shape.
* @param glMode the GL mode - can be one of <code>GL.GL_TRIANGLE_FAN</code> and <code>GL.LINE_STRIP</code>.
* @param cornerRadius the rounded corners radius. Set to zero for square corners.
*/
public static void drawShape(DrawContext dc, String shape, double width, double height, int glMode, int cornerRadius)
{
if (!shape.equals(SHAPE_NONE))
drawBuffer(dc, glMode, createShapeBuffer(shape, width, height, cornerRadius));
}
/**
* Draw a shape with the specified width and height, gl mode and corner radius. The shape includes a
* leader triangle pointing to a specified point. GL mode came be one of <code>GL.GL_TRIANGLE_FAN</code>
* and <code>GL.LINE_STRIP</code>. Corner radius only apply to <code>SHAPE_RECTANGLE</code> - set to zero for square corners.
* @param dc the current <code>DrawContext</code>.
* @param shape the shape - can be one of <code>SHAPE_RECTANGLE</code> or <code>SHAPE_ELLIPSE</code>.
* @param width the width of the overall shape.
* @param height the height of the shape excluding the leader.
* @param leaderOffset the coordinates of the point to which the leader leads.
* @param glMode the GL mode - can be one of <code>GL.GL_TRIANGLE_FAN</code> and <code>GL.LINE_STRIP</code>.
* @param cornerRadius the rounded corners radius. Set to zero for square corners.
*/
public static void drawShapeWithLeader(DrawContext dc, String shape, double width, double height, Point leaderOffset, int glMode, int cornerRadius)
{
if (!shape.equals(SHAPE_NONE))
drawBuffer(dc, glMode, createShapeWithLeaderBuffer(shape, width, height, leaderOffset, cornerRadius));
}
/**
* Create a vertex buffer for a shape with the specified width, height and corner radius. Corner radius only apply
* to <code>SHAPE_RECTANGLE</code> - set to zero for square corners.
* @param shape the shape - can be one of <code>SHAPE_RECTANGLE</code> or <code>SHAPE_ELLIPSE</code>.
* @param width the width of the overall shape.
* @param height the height of the shape.
* @param cornerRadius the rounded corners radius. Set to zero for square corners.
* @return the vertex buffer.
*/
public static DoubleBuffer createShapeBuffer(String shape, double width, double height, int cornerRadius)
{
if (shape.equals(SHAPE_RECTANGLE))
return createRoundedRectangleBuffer(width, height, cornerRadius);
else if (shape.equals(SHAPE_ELLIPSE))
return createEllipseBuffer(width, height, circleSteps);
else if (shape.equals(SHAPE_NONE))
return null;
else
// default to rectangle if shape unknown
return createRoundedRectangleBuffer(width, height, cornerRadius);
}
/**
* Create a vertex buffer for a shape with the specified width, height and corner radius. The shape includes a
* leader triangle pointing to a specified point. Corner radius only apply to <code>SHAPE_RECTANGLE</code>
* - set to zero for square corners.
* @param shape the shape - can be one of <code>SHAPE_RECTANGLE</code> or <code>SHAPE_ELLIPSE</code>.
* @param width the width of the overall shape.
* @param height the height of the shape excluding the leader.
* @param leaderOffset the coordinates of the point to which the leader leads.
* @param cornerRadius the rounded corners radius. Set to zero for square corners.
* @return the vertex buffer.
*/
public static DoubleBuffer createShapeWithLeaderBuffer(String shape, double width, double height, Point leaderOffset, int cornerRadius)
{
if (shape.equals(SHAPE_RECTANGLE))
return createRoundedRectangleWithLeaderBuffer(width, height, leaderOffset, cornerRadius);
else if (shape.equals(SHAPE_ELLIPSE))
return createEllipseWithLeaderBuffer(width, height, leaderOffset, circleSteps);
else if (shape.equals(SHAPE_NONE))
return null;
else
// default to rectangle if shape unknown
return createRoundedRectangleWithLeaderBuffer(width, height, leaderOffset, cornerRadius);
}
/**
* Draw a vertex buffer in a given gl mode. Vertex buffers coming from the createShapeBuffer() methods support
* both <code>GL.GL_TRIANGLE_FAN</code> and <code>GL.LINE_STRIP</code>.
* @param dc the current DrawContext.
* @param glMode the desired drawing GL mode.
* @param verts the vertex buffer to draw.
*/
public static void drawBuffer(DrawContext dc, int glMode, DoubleBuffer verts)
{
if (dc == null)
{
String message = Logging.getMessage("nullValue.DrawContextIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (verts == null)
{
String message = Logging.getMessage("nullValue.BufferIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
GL gl = dc.getGL();
// Set up
gl.glPushClientAttrib(GL.GL_CLIENT_VERTEX_ARRAY_BIT);
gl.glEnableClientState(GL.GL_VERTEX_ARRAY);
gl.glVertexPointer(2, GL.GL_DOUBLE, 0, verts.rewind());
// Draw
gl.glDrawArrays(glMode, 0, verts.limit() / 2);
// Restore
gl.glPopClientAttrib();
}
/**
* Draw a vertex buffer with texture coordinates in a given gl mode. Vertex buffers coming from the
* createShapeBuffer() methods support both <code>GL.GL_TRIANGLE_FAN</code> and <code>GL.LINE_STRIP</code>.
* @param dc the current DrawContext.
* @param glMode the desired drawing GL mode.
* @param verts the vertex buffer to draw.
*/
public static void drawBuffer(DrawContext dc, int glMode, DoubleBuffer verts, DoubleBuffer coords)
{
if (dc == null)
{
String message = Logging.getMessage("nullValue.DrawContextIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (verts == null || coords == null)
{
String message = Logging.getMessage("nullValue.BufferIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
GL gl = dc.getGL();
// Set up
gl.glPushClientAttrib(GL.GL_CLIENT_VERTEX_ARRAY_BIT);
gl.glEnableClientState(GL.GL_VERTEX_ARRAY);
gl.glVertexPointer(2, GL.GL_DOUBLE, 0, verts.rewind());
gl.glEnableClientState(GL.GL_TEXTURE_COORD_ARRAY);
gl.glTexCoordPointer(2, GL.GL_DOUBLE, 0, coords.rewind());
// Draw
gl.glDrawArrays(glMode, 0, verts.limit() / 2);
// Restore
gl.glPopClientAttrib();
}
//-- Shape creation
//-- Rectangle ------------------------------------------------------------------
private static DoubleBuffer createRoundedRectangleBuffer(double width, double height, int cornerRadius)
{
int numVertices = 9 + (cornerRadius < 1 ? 0 : 4 * (cornerSteps - 2));
DoubleBuffer verts = BufferUtil.newDoubleBuffer(numVertices * 2);
int idx = 0;
// Drawing counter clockwise from bottom-left
// Bottom
verts.put(idx++, (double)cornerRadius);
verts.put(idx++, 0d);
verts.put(idx++, width - cornerRadius);
verts.put(idx++, 0d);
idx = drawCorner(width - cornerRadius, cornerRadius, cornerRadius, -Math.PI / 2, 0, cornerSteps, verts, idx);
// Right
verts.put(idx++, width);
verts.put(idx++, (double)cornerRadius);
verts.put(idx++, width);
verts.put(idx++, height - cornerRadius);
idx = drawCorner(width - cornerRadius, height - cornerRadius, cornerRadius, 0, Math.PI / 2, cornerSteps, verts, idx);
// Top
verts.put(idx++, width - cornerRadius);
verts.put(idx++, height);
verts.put(idx++, (double)cornerRadius);
verts.put(idx++, height);
idx = drawCorner(cornerRadius, height - cornerRadius, cornerRadius, Math.PI / 2, Math.PI, cornerSteps, verts, idx);
// Left
verts.put(idx++, 0d);
verts.put(idx++, height - cornerRadius);
verts.put(idx++, 0d);
verts.put(idx++, (double)cornerRadius);
idx = drawCorner(cornerRadius, cornerRadius, cornerRadius, Math.PI, Math.PI * 1.5, cornerSteps, verts, idx);
// Finish up to starting point
verts.put(idx++, (double)cornerRadius);
verts.put(idx++, 0d);
return verts;
}
private static DoubleBuffer createRoundedRectangleWithLeaderBuffer(double width, double height, Point leaderOffset, int cornerRadius)
{
int numVertices = 12 + (cornerRadius < 1 ? 0 : 4 * (cornerSteps - 2));
DoubleBuffer verts = BufferUtil.newDoubleBuffer(numVertices * 2);
int idx = 0;
// Drawing counter clockwise from right leader connection at the bottom
// so as to accomodate GL_TRIANGLE_FAN and GL_LINE_STRIP (inside and border)
// Bottom right
verts.put(idx++, width / 2 + leaderGapWidth / 2);
verts.put(idx++, 0d);
verts.put(idx++, width - cornerRadius);
verts.put(idx++, 0d);
idx = drawCorner(width - cornerRadius, cornerRadius, cornerRadius, -Math.PI / 2, 0, cornerSteps, verts, idx);
// Right
verts.put(idx++, width);
verts.put(idx++, (double)cornerRadius);
verts.put(idx++, width);
verts.put(idx++, height - cornerRadius);
idx = drawCorner(width - cornerRadius, height - cornerRadius, cornerRadius, 0, Math.PI / 2, cornerSteps, verts, idx);
// Top
verts.put(idx++, width - cornerRadius);
verts.put(idx++, height);
verts.put(idx++, (double)cornerRadius);
verts.put(idx++, height);
idx = drawCorner(cornerRadius, height - cornerRadius, cornerRadius, Math.PI / 2, Math.PI, cornerSteps, verts, idx);
// Left
verts.put(idx++, 0d);
verts.put(idx++, height - cornerRadius);
verts.put(idx++, 0d);
verts.put(idx++, (double)cornerRadius);
idx = drawCorner(cornerRadius, cornerRadius, cornerRadius, Math.PI, Math.PI * 1.5, cornerSteps, verts, idx);
// Bottom left
verts.put(idx++, (double)cornerRadius);
verts.put(idx++, 0d);
verts.put(idx++, width / 2 - leaderGapWidth / 2);
verts.put(idx++, 0d);
// Draw leader
verts.put(idx++, leaderOffset.x);
verts.put(idx++, leaderOffset.y);
verts.put(idx++, width / 2 + leaderGapWidth / 2);
verts.put(idx++, 0d);
return verts;
}
private static int drawCorner(double x0, double y0, double cornerRadius, double start, double end, int steps, DoubleBuffer verts, int startIdx)
{
if(cornerRadius < 1)
return startIdx;
GL gl = GLU.getCurrentGL();
double step = (end - start) / (steps - 1);
for(int i = 1; i < steps - 1; i++)
{
double a = start + step * i;
double x = x0 + Math.cos(a) * cornerRadius;
double y = y0 + Math.sin(a) * cornerRadius;
verts.put(startIdx++, x);
verts.put(startIdx++, y);
}
return startIdx;
}
//-- Circle / Ellipse -----------------------------------------------------------
private static DoubleBuffer createEllipseBuffer(double width, double height, int steps)
{
int numVertices = steps + 1;
DoubleBuffer verts = BufferUtil.newDoubleBuffer(numVertices * 2);
int idx = 0;
// Drawing counter clockwise from bottom-left
double halfWidth = width / 2;
double halfHeight = height / 2;
double halfPI = Math.PI / 2;
double x0 = halfWidth;
double y0 = halfHeight;
double step = Math.PI * 2 / steps;
for(int i = 0; i <= steps; i++)
{
double a = step * i - halfPI;
double x = x0 + Math.cos(a) * halfWidth;
double y = y0 + Math.sin(a) * halfHeight;
verts.put(idx++, x);
verts.put(idx++, y);
}
return verts;
}
private static DoubleBuffer createEllipseWithLeaderBuffer(double width, double height, Point leaderOffset, int steps)
{
int numVertices = steps + 3;
DoubleBuffer verts = BufferUtil.newDoubleBuffer(numVertices * 2);
int idx = 0;
// Drawing counter clockwise from right leader connection at the bottom
// so as to accomodate GL_TRIANGLE_FAN and GL_LINE_STRIP (inside and border)
double halfWidth = width / 2;
double halfHeight = height / 2;
double halfPI = Math.PI / 2;
double x0 = halfWidth;
double y0 = halfHeight;
double step = Math.PI * 2 / steps;
double halfGap = leaderGapWidth / 2 / halfWidth;
for(int i = 0; i <= steps; i++)
{
double a = step * i - halfPI;
if (i == 0) a += halfGap;
if (i == steps) a -= halfGap;
double x = x0 + Math.cos(a) * halfWidth;
double y = y0 + Math.sin(a) * halfHeight;
verts.put(idx++, x);
verts.put(idx++, y);
}
// Draw leader
verts.put(idx++, leaderOffset.x);
verts.put(idx++, leaderOffset.y);
verts.put(idx++, x0 + Math.cos(halfGap - halfPI) * halfWidth);
verts.put(idx++, y0 + Math.sin(halfGap - halfPI) * halfHeight);
return verts;
}
//-- Texture coordinates --------------------------------------------------------
/**
* Computes texture coordinates for a vertex buffer, a dimension and a texture size. Coordinates are computed
* so that the texture image proportions and size are preserved. The texture is aligned at top left corner
* of the vertices bounding rectangle.
*
* @param verts the vertex buffer containing the vertices for which texture coordinates have to be computed.
* @param width the vertices bounding rectangle width - excluding the leader if any.
* @param height the vertices bounding rectangle height - excluding the leader if any.
* @param textureWidth the texture width
* @param textureHeight the texture height
* @return the texture coordinates DoubleBuffer
*/
public static DoubleBuffer getTextureCoordinates(DoubleBuffer verts, double width, double height, double textureWidth, double textureHeight)
{
if (verts == null)
return null;
int numVertices = verts.limit() / 2 ;
DoubleBuffer coords = BufferUtil.newDoubleBuffer(numVertices * 2);
int idx = 0;
for(int i = 0; i < verts.limit(); i +=2)
{
// Top-left aligned
coords.put(idx++, verts.get(i) / textureWidth); // Tu
coords.put(idx++, (height - verts.get(i + 1)) / textureHeight); // Tv
}
return coords;
}
}