Hello,
since a few days, I try to incorporate font outlines in my 3D graphics. The code is a bit lengthy and pasted below together with a report that allows to double check that enough points are fed into OpenGL commands. Here are the main steps in the code:
1) get a render context and create a glyph vector for a string in a given font
2) get a path iterator from a shape generated by the glyph vector
3) loop along the iterator and emit reports as well as OpenGL-commands to draw
Unfortunately, two problems still remain:
1) GL11.glEvalCoord1f generates straight lines instead of quadratic or cubic curves
2) Is there a way to avoid glu tesselation to render glyphs with holes?
Any help is appreciated, thanks
Stephan
@Override protected void paintGL() {
...
Font font = new Font("Courier New",Font.PLAIN,2);
drawString("B", font);
...
}
private void drawString(String text, Font font) {
//AWT part
Graphics2D gr = (Graphics2D)getGraphics();
//rendering hints
gr.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
gr.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
gr.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
RenderingHints.VALUE_FRACTIONALMETRICS_ON);
gr.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BICUBIC);
gr.setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
//get shape
FontRenderContext frc = gr.getFontRenderContext();
GlyphVector gv = font.createGlyphVector(frc,text);
Shape shape = gv.getOutline();
AffineTransform trans = new AffineTransform();//identity matrix
//PathIterator iter = shape.getPathIterator(trans, 0.25);
//flatness seems to have no effect
PathIterator iter = shape.getPathIterator(trans);
gr.dispose();
//OpenGL part
float[] coords = new float[6];
FloatBuffer fb2 = BufferUtils.createFloatBuffer(9),
fb3 = BufferUtils.createFloatBuffer(12);
float[] last = new float[3], start = new float[3];
float[] points = new float[9];
int lineType;
//attributes for demonstration
GL11.glPushAttrib(GL11.GL_LINE_BIT);
GL11.glLineWidth(15.0f);
//draw
while(!iter.isDone()) {
lineType = iter.currentSegment(coords);
switch(lineType) {
case PathIterator.SEG_CLOSE:
report("SEG_CLOSE"); report();
GL11.glVertex3f(start[0], start[1], 0.0f);
GL11.glEnd(); break;
case PathIterator.SEG_CUBICTO:
report("SEG_CUBICTO: ");
fb3.clear(); fb3.put(last);
points[0] = coords[0]; points[1] = coords[1];
points[3] = coords[2]; points[4] = coords[3];
points[6] = coords[4]; points[7] = coords[5];
fb3.put(points); fb3.rewind(); report(fb3);
GL11.glMap1f(GL11.GL_MAP1_VERTEX_3,
0.0f, 1.0f, 3, 4, fb3);
GL11.glEnable(GL11.GL_MAP1_VERTEX_3);
for(int no = 0; no<10; ++no)
GL11.glEvalCoord1f((float)(no/10.0));
GL11.glDisable(GL11.GL_MAP1_VERTEX_3);
last[0] = coords[4]; last[1] = coords[5];
break;
case PathIterator.SEG_LINETO:
report("SEG_LINETO: ");
report("{{"+coords[0]+","+coords[1]+", 0.0}}");
report();
last[0] = coords[0]; last[1] = coords[1];
GL11.glVertex3f(last[0], last[1], 0.0f); break;
case PathIterator.SEG_MOVETO:
report("SEG_MOVETO: ");
report("{{"+coords[0]+","+coords[1]+", 0.0}}");
report();
start[0] = coords[0]; start[1] = coords[1];
last[0] = coords[0]; last[1] = coords[1];
GL11.glBegin(GL11.GL_LINE_STRIP);
GL11.glVertex3f(last[0], last[1], 0.0f); break;
case PathIterator.SEG_QUADTO:
report("SEG_QUADTO: ");
fb2.clear(); fb2.rewind(); fb2.put(last);
points[0] = coords[0]; points[1] = coords[1];
points[3] = coords[2]; points[4] = coords[3];
fb2.put(points, 0, 6); fb2.rewind(); report(fb2);
GL11.glMap1f(GL11.GL_MAP1_VERTEX_3,
0.0f, 1.0f, 3, 3, fb2);
GL11.glEnable(GL11.GL_MAP1_VERTEX_3);
for(int no = 0; no<10; ++no)
GL11.glEvalCoord1f((float)(no/10.0));
GL11.glDisable(GL11.GL_MAP1_VERTEX_3);
last[0] = coords[2]; last[1] = coords[3];
break;
}
iter.next();
}
//restore
GL11.glPopAttrib();
}
----
output of report
SEG_MOVETO: {{0.25,-0.078125, 0.0}}
SEG_LINETO: {{0.25,-1.0625, 0.0}}
SEG_LINETO: {{0.140625,-1.0625, 0.0}}
SEG_QUADTO: {{0.140625, -1.0625, 0.0}, {0.109375, -1.0625, 0.0}, {0.09375, -1.0703125, 0.0}
SEG_QUADTO: {{0.09375, -1.0703125, 0.0}, {0.078125, -1.078125, 0.0}, {0.078125, -1.109375, 0.0}
SEG_QUADTO: {{0.078125, -1.109375, 0.0}, {0.078125, -1.125, 0.0}, {0.09375, -1.1328125, 0.0}
SEG_QUADTO: {{0.09375, -1.1328125, 0.0}, {0.109375, -1.140625, 0.0}, {0.140625, -1.140625, 0.0}
SEG_LINETO: {{0.65625,-1.140625, 0.0}}
SEG_QUADTO: {{0.65625, -1.140625, 0.0}, {0.8125, -1.140625, 0.0}, {0.90625, -1.0546875, 0.0}
SEG_QUADTO: {{0.90625, -1.0546875, 0.0}, {1.0, -0.96875, 0.0}, {1.0, -0.84375, 0.0}
SEG_QUADTO: {{1.0, -0.84375, 0.0}, {1.0, -0.703125, 0.0}, {0.84375, -0.609375, 0.0}
SEG_QUADTO: {{0.84375, -0.609375, 0.0}, {0.96875, -0.5625, 0.0}, {1.03125, -0.484375, 0.0}
SEG_QUADTO: {{1.03125, -0.484375, 0.0}, {1.09375, -0.40625, 0.0}, {1.09375, -0.3125, 0.0}
SEG_QUADTO: {{1.09375, -0.3125, 0.0}, {1.09375, -0.234375, 0.0}, {1.0546875, -0.1640625, 0.0}
SEG_QUADTO: {{1.0546875, -0.1640625, 0.0}, {1.015625, -0.09375, 0.0}, {0.9296875, -0.046875, 0.0}
SEG_QUADTO: {{0.9296875, -0.046875, 0.0}, {0.84375, 0.0, 0.0}, {0.75, 0.0, 0.0}
SEG_LINETO: {{0.140625,0.0, 0.0}}
SEG_QUADTO: {{0.140625, 0.0, 0.0}, {0.109375, 0.0, 0.0}, {0.09375, -0.015625, 0.0}
SEG_QUADTO: {{0.09375, -0.015625, 0.0}, {0.078125, -0.03125, 0.0}, {0.078125, -0.046875, 0.0}
SEG_QUADTO: {{0.078125, -0.046875, 0.0}, {0.078125, -0.0625, 0.0}, {0.09375, -0.0703125, 0.0}
SEG_QUADTO: {{0.09375, -0.0703125, 0.0}, {0.109375, -0.078125, 0.0}, {0.140625, -0.078125, 0.0}
SEG_LINETO: {{0.25,-0.078125, 0.0}}
SEG_CLOSE
SEG_MOVETO: {{0.328125,-0.640625, 0.0}}
SEG_LINETO: {{0.625,-0.640625, 0.0}}
SEG_QUADTO: {{0.625, -0.640625, 0.0}, {0.71875, -0.640625, 0.0}, {0.796875, -0.671875, 0.0}
SEG_QUADTO: {{0.796875, -0.671875, 0.0}, {0.859375, -0.703125, 0.0}, {0.890625, -0.75, 0.0}
SEG_QUADTO: {{0.890625, -0.75, 0.0}, {0.921875, -0.796875, 0.0}, {0.921875, -0.859375, 0.0}
SEG_QUADTO: {{0.921875, -0.859375, 0.0}, {0.921875, -0.9375, 0.0}, {0.8515625, -1.0, 0.0}
SEG_QUADTO: {{0.8515625, -1.0, 0.0}, {0.78125, -1.0625, 0.0}, {0.65625, -1.0625, 0.0}
SEG_LINETO: {{0.328125,-1.0625, 0.0}}
SEG_LINETO: {{0.328125,-0.640625, 0.0}}
SEG_CLOSE
SEG_MOVETO: {{0.328125,-0.078125, 0.0}}
SEG_LINETO: {{0.75,-0.078125, 0.0}}
SEG_QUADTO: {{0.75, -0.078125, 0.0}, {0.828125, -0.078125, 0.0}, {0.8828125, -0.1171875, 0.0}
SEG_QUADTO: {{0.8828125, -0.1171875, 0.0}, {0.9375, -0.15625, 0.0}, {0.96875, -0.203125, 0.0}
SEG_QUADTO: {{0.96875, -0.203125, 0.0}, {1.0, -0.25, 0.0}, {1.0, -0.3125, 0.0}
SEG_QUADTO: {{1.0, -0.3125, 0.0}, {1.0, -0.375, 0.0}, {0.9609375, -0.4296875, 0.0}
SEG_QUADTO: {{0.9609375, -0.4296875, 0.0}, {0.921875, -0.484375, 0.0}, {0.84375, -0.515625, 0.0}
SEG_QUADTO: {{0.84375, -0.515625, 0.0}, {0.765625, -0.546875, 0.0}, {0.625, -0.546875, 0.0}
SEG_LINETO: {{0.328125,-0.546875, 0.0}}
SEG_LINETO: {{0.328125,-0.078125, 0.0}}
SEG_CLOSE
----
private void report(float number) {System.out.print(number);}
private void report() {System.out.println();}
private void report(String text) {System.out.print(text);}
private void report(FloatBuffer fb) {
report("{{");
for(int no = 0; no < fb.limit()-1; ++no) {
report(fb.get(no));
if(no%3==2) report("}, {"); else report(", ");
}
report(fb.get(fb.limit()-1)+"}");
report();
}
Are you still working on this? Do you have a complete demo program that I can experiment with?
Quote from: "baysmith"Are you still working on this? Do you have a complete demo program that I can experiment with?
Hello baysmith,
some missing GLU functions in LWJGL caused my switch to JOGL. Below is a sketch of how I got 3D fonts in JOGL (quick solution, appended, because it is a bit lengthy). I hope that you will find the code helpful to find a solution within the scope of LWJGL (I did not succeed in a reasonable time). The code is incorporated in a larger project. Background can be found in the "redbook": Shreiner/Woo/Neider/Davis, OpenGL Programming Guide, Addison-Wesley, 5th ed 2006 (very good, 800 pages, covers version 2).
Regards Stephan
1) Get the shape from AWT
types: java.awt.Graphics2D gr, java.awt.Font font, java.awt.font.GlyphVector gv, java.awt.Shape shape, java.awt.geom.AffineTransform trans, java.awt.geom.PathIterator iter
//get shape
FontRenderContext frc = gr.getFontRenderContext();
GlyphVector gv = font.createGlyphVector(frc,text);
Shape shape = gv.getOutline();
AffineTransform trans = new AffineTransform();//identity matrix
//PathIterator iter = shape.getPathIterator(trans, 0.25);
//flatness seems to have no effect
PathIterator iter = shape.getPathIterator(trans);
gr.dispose();
2) Use the shape to tesselate
report(): in essence, same functionality as println, useful for debugging
types (for speed at object level):
private double[][] quadraticControlPoints = new double[3][3];
private double[][] cubicControlPoints = new double[4][3];
private double[] point = new double[3];
double[] coords = new double[6];
double[] last = new double[3], start = new double[3];
code:
gl = drawable.getGL();
int lineType;
//attributes
gl.glPushAttrib(GL.GL_LINE_BIT);
gl.glLineWidth(15.0f);
//tesselator
glu = drawable.getGLU();
GLUtesselator tesselator = glu.gluNewTess();
glu.gluTessCallback(tesselator, GLU.GLU_TESS_BEGIN, this);
glu.gluTessCallback(tesselator, GLU.GLU_TESS_VERTEX, this);
glu.gluTessCallback(tesselator, GLU.GLU_TESS_COMBINE, this); glu.gluTessCallback(tesselator, GLU.GLU_TESS_END, this); glu.gluTessCallback(tesselator, GLU.GLU_TESS_ERROR, this); switch(iter.getWindingRule()) {
case PathIterator.WIND_EVEN_ODD:
report("Even/odd shape winding");
glu.gluTessProperty(tesselator,
GLU.GLU_TESS_WINDING_RULE,
GLU.GLU_TESS_WINDING_ODD);
break;
case PathIterator.WIND_NON_ZERO:
report("Nonzero shape winding");
glu.gluTessProperty(tesselator,
GLU.GLU_TESS_WINDING_RULE,
GLU.GLU_TESS_WINDING_NONZERO);
break;
}
report();
//draw (points to glu tesselator must be independent objects)
gl.glNewList(getDisplayList(), GL.GL_COMPILE);
Object userData = null;
double[] vertexData = null;
glu.gluTessBeginPolygon(tesselator, userData);
while(!iter.isDone()) {
lineType = iter.currentSegment(coords);
switch(lineType) {
case PathIterator.SEG_CLOSE: report("SEG_CLOSE");
report();
//vertexData = start;
//glu.gluTessVertex(tesselator, start, vertexData);
glu.gluTessEndContour(tesselator);
break;
case PathIterator.SEG_CUBICTO: report("SEG_CUBICTO: ");
cubicControlPoints[0][0] = last[0];
cubicControlPoints[0][1] = last[1];
cubicControlPoints[1][0] = coords[0];
cubicControlPoints[1][1] = coords[1];
cubicControlPoints[2][0] = coords[2];
cubicControlPoints[2][1] = coords[3];
cubicControlPoints[3][0] = coords[4];
cubicControlPoints[3][1] = coords[5];
report(cubicControlPoints);
report(" -> {");
for(int no = 0; no<10; ++no) {
point = cubic.point(cubicControlPoints,no/10.0);
vertexData = point;
report(point); report(", ");
glu.gluTessVertex(tesselator, point, vertexData);
}
report("}");report();
last[0] = coords[4]; last[1] = coords[5];
break;
case PathIterator.SEG_LINETO: report("SEG_LINETO: ");
report("{{");report(coords[0]); report(",");
report(coords[1]); report(", 0.0}}"); report();
last[0] = coords[0]; last[1] = coords[1];
vertexData = last.clone();
glu.gluTessVertex(tesselator, vertexData, vertexData);
break;
case PathIterator.SEG_MOVETO: report("SEG_MOVETO: ");
report("{{");report(coords[0]); report(",");
report(coords[1]); report(", 0.0}}"); report();
start[0] = coords[0]; start[1] = coords[1];
last[0] = coords[0]; last[1] = coords[1];
vertexData = last.clone();
glu.gluTessBeginContour(tesselator);
glu.gluTessVertex(tesselator, vertexData, vertexData);
break;
case PathIterator.SEG_QUADTO: report("SEG_QUADTO: ");
quadraticControlPoints[0][0] = last[0];
quadraticControlPoints[0][1] = last[1];
quadraticControlPoints[1][0] = coords[0];
quadraticControlPoints[1][1] = coords[1];
quadraticControlPoints[2][0] = coords[2];
quadraticControlPoints[2][1] = coords[3];
report(quadraticControlPoints);
report(" -> {");
for(int no = 0; no<10; ++no) {
point = quadratic.point(quadraticControlPoints,
no/10.0);
vertexData = point;
report(point); report(", ");
glu.gluTessVertex(tesselator, point, vertexData);
}
report("}");report();
last[0] = coords[2]; last[1] = coords[3];
break;
}
iter.next();
}
glu.gluTessEndPolygon(tesselator);
gl.glEndList();
//restore
gl.glPopAttrib();
tesselator callbacks:
public void begin(int primitiveType) {
switch(primitiveType) {
case GL.GL_TRIANGLE_FAN: report("Triangle fan: "); break;
case GL.GL_TRIANGLE_STRIP: report("Triangle strip: "); break;
case GL.GL_TRIANGLES: report("Triangles: "); break;
case GL.GL_LINE_LOOP: report("Line loop: "); break;
default: report("Start primitive "+primitiveType+" ");
}
gl.glBegin(primitiveType);
}
public void beginData(int primitiveType, Object userData) {
report("Start primitive "+primitiveType+
" with user data "+userData);
gl.glBegin(primitiveType);
}
public void edgeFlag(boolean edge) {
if(edge) report("Draw edge "); else report("Draw no edge ");
/*only for GL_TRIANGLES*/
}
public void edgeFlagData(boolean edge, Object userData) {
if(edge) report("Draw edge with user data "+userData+" ");
else report("Draw no edge with user data "+userData+" ");
/*only for GL_TRIANGLES*/
}
public void vertex(Object vertexData) {
double[] coords = (double[])vertexData;
report("V "); report(coords); report(" ");
gl.glVertex3dv(coords);
}
public void vertexData(Object vertexData, Object userData) {
double[] coords = (double[])vertexData;
report("V "); report(coords); report(" UD "+userData);
report(" ");
gl.glVertex3dv(coords);
}
public void end() {
report("End primitive"); report();
gl.glEnd();
}
public void endData(Object userData) {
report("End primitive with user data "+userData); report();
gl.glEnd();
}
public void combine(
double[] position3DVertex,
Object[] dataOfFourNeighbourVertices, //some may be null
float[] weightsOfFourNeighbourVertices,
Object[] dataForSplitOrMergeVertex //length 1
) {
report("Combine at position "); report(position3DVertex);
double[] vertex = new double[3];
for(int no = 0; no<3; ++no) vertex[no] = position3DVertex[no];
dataForSplitOrMergeVertex[0] = vertex;
}
public void combineData(
double[] position3DVertex,
Object[] dataOfFourNeighbourVertices, //some may be null
float[] weightsOfFourNeighbourVertices,
Object[] dataForSplitOrMergeVertex, //length 1
Object userData
) {
report("Combine at position "); report(position3DVertex);
report(" with user data "+userData);
double[] vertex = new double[3];
for(int no = 0; no<3; ++no) vertex[no] = position3DVertex[no];
dataForSplitOrMergeVertex[0] = vertex;
}
public void error(int errorNumber) {
System.out.println("Tesselation error "+errorNumber+
" in Text: "+glu.gluErrorString(errorNumber));
}
public void errorData(int errorNumber, Object userData) {
System.out.println("Tesselation error "+errorNumber+
" in Text: "+glu.gluErrorString(errorNumber));
}