package glapp; import java.util.ArrayList; import org.lwjgl.opengl.GL11; import glmodel.*; /** * GLLine creates a quad-strip line from a series of points. To use: *
* draw() {
* GLLine L = new GLLine();
* L.setWidth(20);
* L.point(0,0);
* L.point(5,5);
* L.point(0,10);
* L.point(5,15);
* L.close(); // if you want a closed shape
* L.draw();
* }
*
* * NOTE: gets ugly when points are very close, or reverse direction sharply. Uses * my home-grown trigonometry... probably not the best way to do this, but it works * in most cases. *
* ALSO NOTE: the line will be drawn with the active texture. To draw an untextured
* line, call activateTexture(0) or disable textures (GL11.glDisable(GL11.GL_TEXTURE_2D)
* before calling line.draw().
*/
public class GLLine {
ArrayList points; // holds each point in line (see point())
float w2 = 1f; // half of line width
boolean changed = false; // true if line was changed since last rebuild()
float uvfactor = 100f; // how to tile texture on line: if uvfactor=100 texture will repeat every 100 units
float[][] verts; // to hold vertex positions
private int vc; // vertex counter
public GLLine() {
clear();
}
public GLLine(float lineWidth) {
clear();
setWidth(lineWidth);
}
/**
* reset the line
*/
public void clear() {
points = new ArrayList();
verts = null;
}
/**
* Add an xy point to the line.
*/
public void point(float x, float y) {
GL_Vector p = new GL_Vector(x,y,0);
/*
// eliminate segments shorter than 10 units
// in hand drawn lines very short segments (around 1 unit long) give erratic results
if (points.size() > 0) {
if ( GL_Vector.sub(p,(GL_Vector)points.get(points.size()-1)).length() < 10) {
return;
}
}
*/
points.add(p);
changed = true;
}
/**
* Close the line by adding a line segment that connects the last point to the first.
*/
public void close() {
int numPoints = points.size();
if (numPoints >= 3) {
GL_Vector firstP = (GL_Vector)points.get(0); // first point in shape
GL_Vector lastP = (GL_Vector)points.get(points.size()-1); // last point in shape
// close the shape if not already closed
if (!lastP.equals(firstP)) {
point(firstP.x, firstP.y);
}
}
}
/**
* set line width
*/
public void setWidth(float w) {
w2 = w/2f;
changed = true;
}
/**
* return line width
*/
public float getWidth() {
return w2 * 2;
}
/**
* return number of points in this line
*/
public int getNumPoints() {
return points.size();
}
/**
* return the given xy point
*/
public GL_Vector getPoint(int n) {
return (n > 0 && n < points.size())? (GL_Vector)points.get(n) : null;
}
/**
* set texture tiling factor. Texture will repeat every
* qv4 p2 qv3
* +----o----+
* -N2 | N2
* |
* | line segment p1-p2
* |
* -N1 | N1
* +----o----+
* qv1 p1 qv2
*
*
* @param p1 start point of line segment
* @param p2 end point of line segment
* @param prevV direction of previous line segment
* @param postV direction of next line segment
*/
public void makeSegment(GL_Vector p1, GL_Vector p2, GL_Vector prevV, GL_Vector postV) {
// vectors of line segments
GL_Vector v0 = prevV; // direction of prior line segment
GL_Vector v1 = GL_Vector.sub(p2,p1); // direction of segment we're drawing now
GL_Vector v2 = postV; // direction of next line segment
// normals to form the line joins
GL_Vector N1 = getNormal(v0,v1,w2); // average normal for previous and current segment
GL_Vector N2 = getNormal(v1,v2,w2); // average normal for current and next segment
// make the four quad verts for the segment
GL_Vector qv2 = GL_Vector.add(p1, N1);
GL_Vector qv1 = GL_Vector.add(p1, N1.reverse());
GL_Vector qv3 = GL_Vector.add(p2, N2);
GL_Vector qv4 = GL_Vector.add(p2, N2.reverse());
// add the verts to array
verts[vc][0] = qv1.x; verts[vc][1] = qv1.y; verts[vc][2] = qv1.z; vc++;
verts[vc][0] = qv2.x; verts[vc][1] = qv2.y; verts[vc][2] = qv2.z; vc++;
verts[vc][0] = qv3.x; verts[vc][1] = qv3.y; verts[vc][2] = qv3.z; vc++;
verts[vc][0] = qv4.x; verts[vc][1] = qv4.y; verts[vc][2] = qv4.z; vc++;
}
/**
* Make an average normal to the two vectors with the right length to
* make a join for a line
* + - - - - - -
* |\ normal
* | \
* | o-----------o line b
* |
* |
* |
* o line a
*
* @param a
* @param b
* @return
*/
public GL_Vector getNormal(GL_Vector a, GL_Vector b, float width) {
// Z axis
GL_Vector Z = new GL_Vector(0,0,1);
// get normal to line a and b (normals are in the XY plane)
GL_Vector cross1 = GL_Vector.crossProduct(a,Z);
GL_Vector cross2 = GL_Vector.crossProduct(b,Z);
// average the two normals
GL_Vector crossAvg = GL_Vector.add(cross1,cross2).div(2).normalize();
// adjust the length of the normal
float N1 = getNormalLen(cross1,crossAvg,width);
crossAvg.mult(N1);
return crossAvg;
}
/**
* Return the length of the normal needed to make a join in a line
* of the given width.
* @param v1
* @param v2
* @param width
* @return
*/
public float getNormalLen(GL_Vector v1, GL_Vector v2, float width) {
float A = 180 - GL_Vector.angleXY(v1,v2);
float normlen = width/(float)Math.cos(Math.toRadians(A));
return normlen;
}
/**
* for debugging
* @param v
* @param x
* @param y
*/
public void drawV(GL_Vector v, float x, float y) {
GL11.glColor3f(0,1,1);
GL11.glPushMatrix(); {
GL11.glBegin(GL11.GL_LINES);
GL11.glVertex3f(x,y,0);
GL11.glVertex3f(x+v.x, y+v.y, 0);
GL11.glEnd();
}
GL11.glPopMatrix();
}
/**
* draw the mesh geometry created by rebuild()
*/
public void draw() {
// rebuild if points were added or width changed
if (changed) {
makeLine();
}
// line is not empty, draw it
if (verts != null && verts.length >= 0) {
// normal is on Z axis
GL11.glNormal3f(0,0,1);
// draw the quads
for (int i=0; i < verts.length; i+=4) {
GL11.glBegin(GL11.GL_QUADS);
{
GL11.glTexCoord2f(verts[i+0][0]/uvfactor,verts[i+0][1]/uvfactor);
GL11.glVertex3f(verts[i+0][0],verts[i+0][1],verts[i+0][2]);
GL11.glTexCoord2f(verts[i+1][0]/uvfactor,verts[i+1][1]/uvfactor);
GL11.glVertex3f(verts[i+1][0],verts[i+1][1],verts[i+1][2]);
GL11.glTexCoord2f(verts[i+2][0]/uvfactor,verts[i+2][1]/uvfactor);
GL11.glVertex3f(verts[i+2][0],verts[i+2][1],verts[i+2][2]);
GL11.glTexCoord2f(verts[i+3][0]/uvfactor,verts[i+3][1]/uvfactor);
GL11.glVertex3f(verts[i+3][0],verts[i+3][1],verts[i+3][2]);
}
GL11.glEnd();
}
}
}
/**
* draw the mesh geometry created by rebuild() as a 3D shape (extrude the line shape
* along the Z axis).
*/
public void draw3D(float height) {
// rebuild if points were added or width changed
if (changed) {
makeLine();
}
// if line is not empty, draw it
if (verts != null && verts.length > 0) {
float z = height; // how high to extrude line
GL_Vector n; // normal of quad
// draw the quads
for (int i=0; i < verts.length; i+=4) {
// quad facing forward
GL11.glNormal3f(0,0,1);
GL11.glBegin(GL11.GL_QUADS);
{
GL11.glTexCoord2f(verts[i+0][0]/uvfactor,verts[i+0][1]/uvfactor);
GL11.glVertex3f(verts[i+0][0],verts[i+0][1],verts[i+0][2]);
GL11.glTexCoord2f(verts[i+1][0]/uvfactor,verts[i+1][1]/uvfactor);
GL11.glVertex3f(verts[i+1][0],verts[i+1][1],verts[i+1][2]);
GL11.glTexCoord2f(verts[i+2][0]/uvfactor,verts[i+2][1]/uvfactor);
GL11.glVertex3f(verts[i+2][0],verts[i+2][1],verts[i+2][2]);
GL11.glTexCoord2f(verts[i+3][0]/uvfactor,verts[i+3][1]/uvfactor);
GL11.glVertex3f(verts[i+3][0],verts[i+3][1],verts[i+3][2]);
}
GL11.glEnd();
// quad facing backward
GL11.glNormal3f(0,0,-1);
GL11.glBegin(GL11.GL_QUADS);
{
GL11.glTexCoord2f(verts[i+1][0]/uvfactor,verts[i+1][1]/uvfactor);
GL11.glVertex3f(verts[i+1][0],verts[i+1][1],verts[i+1][2]-z);
GL11.glTexCoord2f(verts[i+0][0]/uvfactor,verts[i+0][1]/uvfactor);
GL11.glVertex3f(verts[i+0][0],verts[i+0][1],verts[i+0][2]-z);
GL11.glTexCoord2f(verts[i+3][0]/uvfactor,verts[i+3][1]/uvfactor);
GL11.glVertex3f(verts[i+3][0],verts[i+3][1],verts[i+3][2]-z);
GL11.glTexCoord2f(verts[i+2][0]/uvfactor,verts[i+2][1]/uvfactor);
GL11.glVertex3f(verts[i+2][0],verts[i+2][1],verts[i+2][2]-z);
}
GL11.glEnd();
// quad facing side 1 (calc normal from three points on side plane)
n = GL_Vector.getNormal(new GL_Vector(verts[i+3][0],verts[i+3][1],verts[i+3][2]-z),
new GL_Vector(verts[i+0]),
new GL_Vector(verts[i+3]));
GL11.glNormal3f(n.x,n.y,n.z);
GL11.glBegin(GL11.GL_QUADS);
{
GL11.glTexCoord2f(0/uvfactor,verts[i+0][1]/uvfactor);
GL11.glVertex3f(verts[i+0][0],verts[i+0][1],verts[i+0][2]-z);
GL11.glTexCoord2f(z/uvfactor,verts[i+0][1]/uvfactor);
GL11.glVertex3f(verts[i+0][0],verts[i+0][1],verts[i+0][2]);
GL11.glTexCoord2f(z/uvfactor,verts[i+3][1]/uvfactor);
GL11.glVertex3f(verts[i+3][0],verts[i+3][1],verts[i+3][2]);
GL11.glTexCoord2f(0/uvfactor,verts[i+3][1]/uvfactor);
GL11.glVertex3f(verts[i+3][0],verts[i+3][1],verts[i+3][2]-z);
}
GL11.glEnd();
// quad facing side 2 (calc normal from three points on side plane)
n = GL_Vector.getNormal(new GL_Vector(verts[i+1][0],verts[i+1][1],verts[i+1][2]-z),
new GL_Vector(verts[i+1]),
new GL_Vector(verts[i+2]));
GL11.glNormal3f(n.x,n.y,n.z);
GL11.glBegin(GL11.GL_QUADS);
{
GL11.glTexCoord2f(0/uvfactor,verts[i+1][1]/uvfactor);
GL11.glVertex3f(verts[i+1][0],verts[i+1][1],verts[i+1][2]);
GL11.glTexCoord2f(z/uvfactor,verts[i+1][1]/uvfactor);
GL11.glVertex3f(verts[i+1][0],verts[i+1][1],verts[i+1][2]-z);
GL11.glTexCoord2f(z/uvfactor,verts[i+2][1]/uvfactor);
GL11.glVertex3f(verts[i+2][0],verts[i+2][1],verts[i+2][2]-z);
GL11.glTexCoord2f(0/uvfactor,verts[i+2][1]/uvfactor);
GL11.glVertex3f(verts[i+2][0],verts[i+2][1],verts[i+2][2]);
}
GL11.glEnd();
}
}
}
/**
* render the line into a display list and return the list ID.
*/
public int makeDisplayList() {
int DLID = GLApp.beginDisplayList();
draw();
GLApp.endDisplayList();
return DLID;
}
/**
* return a GLLine shaped into a rectangle.
*/
public static GLLine makeRectangle(float x, float y, float w, float h, float linewidth) {
GLLine rect = new GLLine(linewidth);
rect.point(x, y);
rect.point(x+w, y);
rect.point(x+w, y+h);
rect.point(x, y+h);
rect.close();
return rect;
}
/**
* return a GLLine shaped into a circle. Can also make triangle, square, pentagon, etc. by
* giving a low number for numSegments.
*/
public static GLLine makeCircle(float x, float y, float radius, int numSegments, float linewidth) {
int s = 0; // start
int e = 360; // end
int stepSize = 360/numSegments; // degrees per segment
GLLine L = new GLLine(linewidth);
// add first point
float ts = (float) Math.sin(Math.toRadians(s));
float tc = (float) Math.cos(Math.toRadians(s));
L.point(x+tc*radius, y+ts*radius);
// add intermediate points, snap to {step} degrees
while ( (s = ((s+stepSize)/stepSize)*stepSize) < e) {
ts = (float) Math.sin(Math.toRadians(s));
tc = (float) Math.cos(Math.toRadians(s));
L.point(x+tc*radius, y+ts*radius);
}
// add last point
L.close();
return L;
}
}