summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrkwright <[email protected]>2008-06-21 21:35:50 +0000
committerrkwright <[email protected]>2008-06-21 21:35:50 +0000
commitbac504811d3fde4675b9a8f464a74f1c41be77d5 (patch)
tree78155a8391dc3df56e4fb13c6398e303f0633c50
parentb1196e004bdf8cb9c342e96313becb627e32f871 (diff)
Initial checkin of the TextRenderer3D class. This class
provides a true 3D rendering of a text string. Methods are available to draw text, set the extrusion depth as well as compile to a display list rather than draw immediately. Also checked in a simple demo (TestRenderer3D) of the use of the draw() method. As simple as it can be. Also checked in a more complex exampled that demonstrates the compile/call methods and uses animation to bounce a large number of text objects (as display lists) around the screen. git-svn-id: file:///usr/local/projects/SUN/JOGL/git-svn/svn-server-sync/joglutils/trunk@101 83d24430-9974-4f80-8418-2cc3294053b9
-rw-r--r--demos/src/jgudemos/BouncingText3D.java448
-rw-r--r--demos/src/jgudemos/TestRenderer3D.java166
-rw-r--r--src/net/java/joglutils/jogltext/TextRenderer3D.java624
3 files changed, 1238 insertions, 0 deletions
diff --git a/demos/src/jgudemos/BouncingText3D.java b/demos/src/jgudemos/BouncingText3D.java
new file mode 100644
index 0000000..7ab16ab
--- /dev/null
+++ b/demos/src/jgudemos/BouncingText3D.java
@@ -0,0 +1,448 @@
+/**
+ * Simple bouncing text example. Illustrates use of the TextRenderer3D class
+ *
+ * Ric Wright
+ * June 2008
+ */
+package jgudemos;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Frame;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.awt.geom.Rectangle2D;
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.Random;
+
+import javax.media.opengl.GL;
+import javax.media.opengl.GLAutoDrawable;
+import javax.media.opengl.GLCanvas;
+import javax.media.opengl.GLEventListener;
+import javax.media.opengl.glu.GLU;
+import javax.media.opengl.glu.GLUquadric;
+import javax.vecmath.Point3f;
+
+import com.geofx.opengl.util.TextRenderer3D;
+import com.sun.opengl.util.Animator;
+
+/**
+ * Simple class to demonstrate the use of compile/call with
+ * TextRenderer3D
+ *
+ */
+public class BouncingText3D implements GLEventListener
+{
+ TextRenderer3D tr3;
+
+ float[] LightDiffuse = { 1.0f, 1.0f, 1.0f, 1.0f };
+ float[] LightAmbient = { 0.8f, 0.8f, 0.8f, 1.0f };
+ float[] LightPosition = { 1.0f, 1.0f, 1.0f, 0.0f };
+ float[] mat_specular = { 1.0f, 1.0f, 1.0f, 1.0f };
+ float[] mat_ambient_magenta = { 1.0f, 0.0f, 1.0f, 1.0f };
+ float[] mat_shininess = { 100.0f };
+ protected Random random = new Random();
+
+ public static final int NUM_ITEMS = 20;
+ public static final int MAX_ITEMS = 200;
+ private static int numItems = NUM_ITEMS;
+
+ private ArrayList<TextInfo3D> textInfo = new ArrayList<TextInfo3D>();
+
+ private GLU glu = new GLU();
+ protected GLUquadric QUADRIC = glu.gluNewQuadric();
+
+ /**
+ * Main entry point for the app. The only argument that is parsed
+ * out is the number of items
+ * @param args
+ */
+ public static void main(String[] args)
+ {
+ if (args != null && Array.getLength(args) > 0)
+ {
+ numItems = Integer.parseInt(args[0].trim());
+ if (numItems == 0)
+ numItems = NUM_ITEMS;
+ else if (numItems > MAX_ITEMS)
+ numItems = MAX_ITEMS;
+ }
+
+ Frame frame = new Frame("Bouncing Text 3D");
+ GLCanvas canvas = new GLCanvas();
+
+ canvas.addGLEventListener(new BouncingText3D());
+ frame.add(canvas);
+ frame.setSize(800, 600);
+ final Animator animator = new Animator(canvas);
+ frame.addWindowListener(new WindowAdapter()
+ {
+
+ @Override
+ public void windowClosing(WindowEvent e)
+ {
+ // Run this on another thread than the AWT event queue to
+ // make sure the call to Animator.stop() completes before
+ // exiting
+ new Thread(new Runnable()
+ {
+
+ public void run()
+ {
+ animator.stop();
+ System.exit(0);
+ }
+ }).start();
+ }
+ });
+
+ // Center frame
+ frame.setLocationRelativeTo(null);
+ frame.setVisible(true);
+ animator.start();
+ }
+
+ /**
+ * Initialize the GL instance. Set up the lights and other
+ * variables and conditions specific to this class
+ */
+ public void init(GLAutoDrawable drawable)
+ {
+ GL gl = drawable.getGL();
+ System.out.println("INIT GL IS: " + gl.getClass().getName());
+
+ System.out.println("init GL called. GL Class: " + gl.getClass().getName()
+ + " and this: " + this.getClass().getName());
+
+ gl.setSwapInterval(1);
+
+ // Setup the drawing area and shading mode
+ gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+ gl.glShadeModel(GL.GL_SMOOTH);
+
+ gl.glLightfv(GL.GL_LIGHT0, GL.GL_DIFFUSE, LightAmbient, 0);
+ gl.glLightfv(GL.GL_LIGHT0, GL.GL_DIFFUSE, LightDiffuse, 0);
+ gl.glLightfv(GL.GL_LIGHT0, GL.GL_POSITION, LightPosition, 0);
+
+ gl.glEnable(GL.GL_DEPTH_TEST);
+ gl.glDepthFunc(GL.GL_LESS);
+
+ gl.glEnable(GL.GL_BLEND);
+ gl.glEnable(GL.GL_LINE_SMOOTH);
+ gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
+
+ // Note that it has to be a TRUETYPE font - not OpenType. Apparently, AWT can't
+ // handle CFF glyphs
+ tr3 = new TextRenderer3D(new Font("Times New Roman", Font.TRUETYPE_FONT, 3), 0.25f);
+
+ // Create random text
+ textInfo.clear();
+ for (int i = 0; i < numItems; i++)
+ {
+ textInfo.add(randomTextInfo());
+ }
+
+ }
+
+ /**
+ * The shape or size of the viewport (client frame) has changed. We need to re-init
+ * the matrix stack, i.e. the GL_PROJECTION and then initialize back to the GL_MODELVIEW
+ */
+ public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height)
+ {
+ GL gl = drawable.getGL();
+
+ if (height <= 0) // avoid a divide by zero error!
+ height = 1;
+
+ final float h = (float) width / (float) height;
+ gl.glViewport(0, 0, width, height);
+ gl.glMatrixMode(GL.GL_PROJECTION);
+ gl.glLoadIdentity();
+ glu.gluPerspective(45.0f, h, 1.0, 20.0);
+
+ gl.glMatrixMode(GL.GL_MODELVIEW);
+ gl.glLoadIdentity();
+ }
+
+ /**
+ * Display needs to be re-rendered. This is where all the heavy-lifting
+ * gets done.
+ */
+ public void display(GLAutoDrawable drawable)
+ {
+ GL gl = drawable.getGL();
+
+ // Clear the drawing area
+ gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
+
+ gl.glMaterialfv(GL.GL_FRONT, GL.GL_SPECULAR, mat_specular, 0);
+ gl.glMaterialfv(GL.GL_FRONT, GL.GL_SHININESS, mat_shininess, 0);
+
+ // Reset the current matrix to the "identity"
+ gl.glLoadIdentity();
+
+ gl.glTranslatef(0.0f, 0.0f, -3.0f);
+ gl.glRotatef(45.0f, 1, 0, 0);
+ gl.glRotatef(-30.0f, 0, 1, 0);
+
+ try
+ {
+ gl.glEnable(GL.GL_LIGHTING);
+ gl.glEnable(GL.GL_LIGHT0);
+
+ drawAxes(gl);
+
+ for (Iterator iter = textInfo.iterator(); iter.hasNext();)
+ {
+ TextInfo3D info = (TextInfo3D) iter.next();
+
+ updateTextInfo( info );
+
+ gl.glPushAttrib(GL.GL_TRANSFORM_BIT);
+ gl.glMatrixMode(GL.GL_MODELVIEW);
+ gl.glPushMatrix();
+ gl.glEnable( GL.GL_NORMALIZE);
+
+ gl.glTranslatef(info.position.x, info.position.y, info.position.z);
+ gl.glRotatef(info.angle.x, 1, 0, 0);
+ gl.glRotatef(info.angle.y, 0, 1, 0);
+ gl.glRotatef(info.angle.z, 0, 0, 1);
+
+ // System.out.println(" x,y,z: " + info.position.x + " " + info.position.y + " " + info.position.z + " angle: " + info.angle );
+
+ gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL.GL_AMBIENT_AND_DIFFUSE, info.material, 0);
+
+ tr3.call(info.index);
+
+ gl.glPopMatrix();
+ gl.glPopAttrib();
+ }
+
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * This method essentially always ignored.
+ */
+ public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged)
+ {
+ }
+
+ //------------------ Private Stuff below here ------------------------------
+ private static final float INIT_ANG_VEL_MAG = 0.3f;
+ private static final float INIT_VEL_MAG = 0.25f;
+ private static final float MAX_BOUNDS = 1.5f;
+ private static final float SCALE_FACTOR = 0.05f;
+
+ // Information about each piece of text
+ private static class TextInfo3D
+ {
+ Point3f angularVelocity;
+ Point3f velocity;
+ Point3f position;
+ Point3f angle;
+ float h;
+ float s;
+ float v;
+ int index; // display list index
+ float curTime; // Cycle the saturation
+ float[] material = new float[4];
+
+ // Cache of the RGB color
+ float r;
+ float g;
+ float b;
+
+ String text;
+ }
+
+ Point3f tmp = new Point3f();
+
+ private void updateTextInfo( TextInfo3D info )
+ {
+ // Update velocities and positions of all text
+ float deltaT = 0.1f;
+
+ // Randomize things a little bit every little once in a while
+ if (random.nextInt(10000) == 0)
+ {
+ info.angularVelocity = randomRotation(INIT_ANG_VEL_MAG, INIT_ANG_VEL_MAG, INIT_ANG_VEL_MAG);
+ info.velocity = randomVelocity(INIT_VEL_MAG, INIT_VEL_MAG, INIT_VEL_MAG);
+ }
+
+ // Now update angles and positions
+ tmp.set(info.angularVelocity);
+ tmp.scale(deltaT*deltaT);
+ info.angle.add(tmp);
+
+ tmp.set(info.velocity);
+ tmp.scale(deltaT);
+ info.position.add(tmp);
+
+ // Wrap angles and positions
+ info.angle.x = clampAngle(info.angle.x);
+ info.angle.y = clampAngle(info.angle.y);
+ info.angle.z = clampAngle(info.angle.z);
+
+ info.velocity.x = clampBounds(info.position.x, info.velocity.x );
+ info.velocity.y = clampBounds(info.position.y, info.velocity.y );
+ info.velocity.z = clampBounds(info.position.z, info.velocity.z );
+ }
+
+ private float clampBounds( float pos, float velocity )
+ {
+ if (pos < -MAX_BOUNDS || pos > MAX_BOUNDS)
+ {
+ velocity *= -1.0f;
+ }
+
+ return velocity;
+ }
+
+ private float clampAngle(float angle)
+ {
+ if (angle < 0)
+ {
+ angle += 360;
+ }
+ else if (angle > 360)
+ {
+ angle -= 360;
+ }
+
+ return angle;
+ }
+
+ private TextInfo3D randomTextInfo()
+ {
+ TextInfo3D info = new TextInfo3D();
+ info.text = randomString();
+ info.angle = randomRotation(INIT_ANG_VEL_MAG, INIT_ANG_VEL_MAG, INIT_ANG_VEL_MAG);
+ info.position = randomVector(MAX_BOUNDS, MAX_BOUNDS, MAX_BOUNDS);
+
+
+ Rectangle2D rect = tr3.getBounds(info.text, SCALE_FACTOR);
+
+ float offX = (float) rect.getCenterX();
+ float offY = (float) rect.getCenterY();
+ float offZ = tr3.getDepth() / 2.0f;
+
+ tr3.setDepth(0.1f + random.nextFloat() * 2.0f);
+ info.index = tr3.compile(info.text, -offX, offY, -offZ, SCALE_FACTOR);
+
+ info.angularVelocity = randomRotation(INIT_ANG_VEL_MAG, INIT_ANG_VEL_MAG, INIT_ANG_VEL_MAG);
+ info.velocity = randomVelocity(INIT_VEL_MAG, INIT_VEL_MAG, INIT_VEL_MAG);
+
+ Color c = randomColor();
+ c.getColorComponents(info.material);
+ // Color doesn't set the opacity,so set it to some random non-zero value
+ info.material[3] = random.nextFloat() * 0.9f + 0.1f;
+
+ return info;
+ }
+
+ private String randomString()
+ {
+ switch (random.nextInt(3))
+ {
+ case 0:
+ return "OpenGL";
+ case 1:
+ return "Java3D";
+ default:
+ return "JOGL";
+ }
+ }
+
+ private Point3f randomVector(float x, float y, float z)
+ {
+ return new Point3f(x * random.nextFloat(), y * random.nextFloat(), z * random.nextFloat());
+ }
+
+ private Point3f randomVelocity(float x, float y, float z)
+ {
+ return new Point3f(x * (random.nextFloat() - 0.5f), y * (random.nextFloat() - 0.5f), z * (random.nextFloat() - 0.5f));
+ }
+
+ private Point3f randomRotation(float x, float y, float z)
+ {
+ return new Point3f(random.nextFloat() * 360.0f, random.nextFloat() * 360.0f, random.nextFloat() * 360.0f);
+ }
+
+ private Color randomColor()
+ {
+ // Get a bright and saturated color
+ float r = 0;
+ float g = 0;
+ float b = 0;
+ float s = 0;
+ do
+ {
+ r = random.nextFloat();
+ g = random.nextFloat();
+ b = random.nextFloat();
+
+ float[] hsb = Color.RGBtoHSB((int) (255.0f * r), (int) (255.0f * g), (int) (255.0f * b), null);
+ s = hsb[1];
+ }
+ while ((r < 0.6f && g < 0.6f && b < 0.6f) || s < 0.8f);
+
+ return new Color(r, g, b);
+ }
+
+ // draw some striped-pole axes for visdual reference
+ protected void drawAxes(GL gl)
+ {
+ float[] mat_ambient_red = { 1.0f, 0.0f, 0.0f, 1.0f };
+ float[] mat_ambient_green = { 0.0f, 1.0f, 0.0f, 1.0f };
+ float[] mat_ambient_blue = { 0.0f, 0.0f, 1.0f, 1.0f };
+
+ drawAxis(gl, 2, mat_ambient_red);
+
+ drawAxis(gl, 0, mat_ambient_blue);
+
+ drawAxis(gl, 1, mat_ambient_green);
+ }
+
+ // draw a single striped pole axis
+ private void drawAxis(GL gl, int rot, float[] material )
+ {
+ float[] mat_ambient_grey = { 0.5f, 0.5f, 0.5f, 1.0f };
+ final double AXIS_RADIUS = 0.01;
+ final int AXIS_HEIGHT = 5;
+ final float AXIS_STEP = 0.25f;
+
+ gl.glPushMatrix();
+
+ if (rot == 1)
+ gl.glRotatef(90, 1, 0, 0);
+ else if (rot == 0)
+ gl.glRotatef(90, 0, 1, 0);
+
+ gl.glTranslatef(0.0f, 0.0f, (float)-AXIS_HEIGHT/2.0f);
+
+ float pos = -AXIS_HEIGHT/2.0f;
+ int i = 0;
+ while ( pos < AXIS_HEIGHT/2.0f )
+ {
+ if ((i++ & 1)==0)
+ gl.glMaterialfv(GL.GL_FRONT, GL.GL_AMBIENT_AND_DIFFUSE, material, 0);
+ else
+ gl.glMaterialfv(GL.GL_FRONT, GL.GL_AMBIENT_AND_DIFFUSE, mat_ambient_grey, 0);
+
+ glu.gluCylinder(QUADRIC, AXIS_RADIUS, AXIS_RADIUS, AXIS_STEP, 8, 1);
+ gl.glTranslatef(0.0f, 0.0f, AXIS_STEP);
+ pos += AXIS_STEP;
+ }
+
+ gl.glPopMatrix();
+ }
+} \ No newline at end of file
diff --git a/demos/src/jgudemos/TestRenderer3D.java b/demos/src/jgudemos/TestRenderer3D.java
new file mode 100644
index 0000000..a084b7c
--- /dev/null
+++ b/demos/src/jgudemos/TestRenderer3D.java
@@ -0,0 +1,166 @@
+/**
+ * Simple test of the TextRenderer3D class.
+ *
+ * Ric Wright
+ * June 2008
+ */
+package jgudemos;
+
+import com.geofx.opengl.util.TextRenderer3D;
+import com.sun.opengl.util.Animator;
+
+import java.awt.Font;
+import java.awt.Frame;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.awt.geom.Rectangle2D;
+
+import javax.media.opengl.GL;
+import javax.media.opengl.GLAutoDrawable;
+import javax.media.opengl.GLCanvas;
+import javax.media.opengl.GLEventListener;
+import javax.media.opengl.glu.GLU;
+
+/**
+ * TestRenderer3D
+ * Hello World style test of the TextRenderer3D
+ *
+ */
+public class TestRenderer3D implements GLEventListener
+{
+ TextRenderer3D tr3;
+ float[] LightDiffuse = { 1.0f, 1.0f, 1.0f, 1.0f };
+ float[] LightAmbient = { 0.8f, 0.8f, 0.8f, 1.0f };
+ float[] LightPosition = { 1.0f, 1.0f, 1.0f, 0.0f };
+ float[] mat_specular = { 1.0f, 1.0f, 1.0f, 1.0f };
+ float[] mat_ambient_magenta = { 1.0f, 0.0f, 1.0f, 1.0f };
+ float[] mat_shininess = { 100.0f };
+
+ public static void main(String[] args)
+ {
+ Frame frame = new Frame("Simple JOGL Application");
+ GLCanvas canvas = new GLCanvas();
+
+ canvas.addGLEventListener(new TestRenderer3D());
+ frame.add(canvas);
+ frame.setSize(640, 480);
+ final Animator animator = new Animator(canvas);
+ frame.addWindowListener(new WindowAdapter()
+ {
+
+ @Override
+ public void windowClosing(WindowEvent e)
+ {
+ // Run this on another thread than the AWT event queue to
+ // make sure the call to Animator.stop() completes before
+ // exiting
+ new Thread(new Runnable()
+ {
+
+ public void run()
+ {
+ animator.stop();
+ System.exit(0);
+ }
+ }).start();
+ }
+ });
+
+ // Center frame
+ frame.setLocationRelativeTo(null);
+ frame.setVisible(true);
+ animator.start();
+ }
+
+ /**
+ * Initialize the GL instance. Set up the lights and other
+ * variables and conditions specific to this class
+ */
+ public void init(GLAutoDrawable drawable)
+ {
+ GL gl = drawable.getGL();
+ System.out.println("init GL called. GL Class: " + gl.getClass().getName()
+ + " and this: " + this.getClass().getName());
+
+ gl.setSwapInterval(1);
+
+ gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+ gl.glShadeModel(GL.GL_SMOOTH);
+
+ gl.glLightfv(GL.GL_LIGHT0, GL.GL_DIFFUSE, LightAmbient, 0);
+ gl.glLightfv(GL.GL_LIGHT0, GL.GL_DIFFUSE, LightDiffuse, 0);
+
+ gl.glLightfv(GL.GL_LIGHT0, GL.GL_POSITION, LightPosition, 0);
+
+ gl.glEnable(GL.GL_DEPTH_TEST);
+ gl.glDepthFunc(GL.GL_LESS);
+
+ // Be sure to use a font name on your system otherwise you will get the default
+ tr3 = new TextRenderer3D(new Font("Times New Roman", Font.TRUETYPE_FONT, 3), 0.25f);
+ }
+
+ /**
+ * The shape or size of the viewport (client frame) has changed. We need to re-init
+ * the matrix stack, i.e. the GL_PROJECTION and then initialize back to the GL_MODELVIEW
+ */
+ public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height)
+ {
+ GL gl = drawable.getGL();
+ GLU glu = new GLU();
+
+ if (height <= 0) // avoid a divide by zero error!
+ height = 1;
+
+ final float h = (float) width / (float) height;
+ gl.glViewport(0, 0, width, height);
+ gl.glMatrixMode(GL.GL_PROJECTION);
+ gl.glLoadIdentity();
+ glu.gluPerspective(45.0f, h, 1.0, 20.0);
+ gl.glMatrixMode(GL.GL_MODELVIEW);
+ gl.glLoadIdentity();
+ }
+
+ /**
+ * Display needs to be re-rendered. This is where all the heavy-lifting
+ * gets done.
+ */
+ public void display(GLAutoDrawable drawable)
+ {
+ GL gl = drawable.getGL();
+
+ // Clear the drawing area
+ gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
+ // Reset the current matrix to the "identity"
+ gl.glLoadIdentity();
+
+ gl.glTranslatef(1.5f, 0.0f, -6.0f);
+ gl.glRotatef(45.0f, 0, 1, 0);
+
+ String str = "abcde";
+ Rectangle2D rect = tr3.getBounds(str, 0.25f);
+
+ float offX = (float) rect.getCenterX();
+ float offY = (float) rect.getCenterY();
+ float offZ = tr3.getDepth() / 2.0f;
+
+ gl.glEnable(GL.GL_LIGHTING);
+ gl.glEnable(GL.GL_LIGHT0);
+
+ gl.glMaterialfv(GL.GL_FRONT, GL.GL_SPECULAR, mat_specular, 0);
+ gl.glMaterialfv(GL.GL_FRONT, GL.GL_SHININESS, mat_shininess, 0);
+ gl.glMaterialfv(GL.GL_FRONT, GL.GL_AMBIENT_AND_DIFFUSE, mat_ambient_magenta, 0);
+
+ tr3.draw(str, -offX, offY, -offZ, 1.0f);
+
+ // Flush all drawing operations to the graphics card
+ gl.glFlush();
+ }
+
+ /**
+ * This method essentially always ignored.
+ */
+ public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged)
+ {
+ }
+} \ No newline at end of file
diff --git a/src/net/java/joglutils/jogltext/TextRenderer3D.java b/src/net/java/joglutils/jogltext/TextRenderer3D.java
new file mode 100644
index 0000000..80f89d8
--- /dev/null
+++ b/src/net/java/joglutils/jogltext/TextRenderer3D.java
@@ -0,0 +1,624 @@
+/*
+ * Copyright (c) 2006 Erik Tollerud ([email protected]) All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistribution of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistribution in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * The names of Erik Tollerud, Davide Raccagni, Sun Microsystems, Inc. or the names of
+ * contributors may not be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
+ * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
+ * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. ERIK TOLLERUD,
+ * SUN MICROSYSTEMS, INC. ("SUN"), AND SUN'S LICENSORS SHALL NOT BE LIABLE FOR
+ * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
+ * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL ERIK
+ * TOLLERUD, SUN, OR SUN'S LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT
+ * OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR
+ * PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
+ * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF ERIK
+ * TOLLERUD OR SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed or intended for use
+ * in the design, construction, operation or maintenance of any nuclear
+ * facility.
+ *
+ *
+ * Cleaned up, added correct normal calculations and removed extraneous stuff.
+ * Also modified method signatures to match, as closely as reasonable, those
+ * of TextRenderer. Also added support for compiling the shapes to display lists
+ * on the fly. Note that the class is now dependent on Java3D for the vecmath
+ * package.
+ * - Ric Wright, [email protected], June 2008
+ */
+
+package net.java.joglutils.jogltext;
+
+import java.awt.Font;
+import java.awt.font.FontRenderContext;
+import java.awt.font.GlyphVector;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.PathIterator;
+import java.awt.geom.Rectangle2D;
+import java.text.StringCharacterIterator;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import javax.media.opengl.GL;
+import javax.media.opengl.glu.GLU;
+import javax.media.opengl.glu.GLUtessellator;
+import javax.media.opengl.glu.GLUtessellatorCallback;
+import javax.vecmath.Vector3f;
+
+/**
+ * This class renders a TrueType Font into OpenGL
+ *
+ * @author Davide Raccagni
+ * @author Erik Tollerud
+ * @created January 29, 2004
+ */
+public class TextRenderer3D
+{
+ private Font font;
+
+ private float depth = 0.1f;
+
+ private boolean edgeOnly = false;
+
+ private boolean calcNormals = true;;
+ private float flatness = 0.0001f;
+
+ private Vector3f vecA = new Vector3f();
+ private Vector3f vecB = new Vector3f();
+ private Vector3f normal = new Vector3f();
+
+ private GLU glu = new GLU();
+ private GL gl = GLU.getCurrentGL();
+
+ private int lastIndex = -1;
+ private ArrayList<Integer> listIndex = new ArrayList<Integer>();
+
+ /**
+ * Intstantiates a new TextRenderer3D initially rendering in the specified
+ * font.
+ *
+ * @param font - the initial font for this TextRenderer3D
+ * depth - the extruded depth for the font
+ * @throws java.lang.NullPointerException
+ * if the supplied font is null
+ */
+ public TextRenderer3D(Font font, float depth) throws NullPointerException
+ {
+ if (font == null)
+ throw new NullPointerException("Can't use a null font to create a TextRenderer3D");
+ this.font = font;
+ this.depth = depth;
+ }
+
+ /**
+ * Specifies which font to render with this TextRenderer3D
+ *
+ * @param font
+ * a font to use for rendering *
+ * @throws java.lang.NullPointerException
+ * if the supplied font is null
+ */
+ public void setFont(Font font) throws NullPointerException
+ {
+ if (font == null)
+ throw new NullPointerException("Can't set a TextRenderer3D font to null");
+ this.font = font;
+ }
+
+ /**
+ * Retrieves the Font currently associated with this TextRenderer3D
+ *
+ * @return the Font in which this object renders strings
+ */
+ public Font getFont()
+ {
+ return this.font;
+ }
+
+ /**
+ * Determines how long the sides of the rendered text is. In the special
+ * case of 0, the rendering is 2D.
+ *
+ * @param depth
+ * specifies the z-size of the rendered 3D text. Negative numbers
+ * will be set to 0.
+ */
+ public void setDepth(float depth)
+ {
+ if (depth <= 0)
+ this.depth = 0;
+ else
+ this.depth = depth;
+ }
+
+ /**
+ * Retrieves the z-depth used for this TextRenderer3D's text rendering.
+ *
+ * @return the z-depth of the rendered 3D text.
+ */
+ public float getDepth()
+ {
+ return this.depth;
+ }
+
+ /**
+ * Sets if the text should be rendered as filled polygons or wireframe.
+ *
+ * @param fill
+ * if true, uses filled polygons, if false, renderings are
+ * wireframe.
+ */
+ public void setFill(boolean fill)
+ {
+ this.edgeOnly = !fill;
+ }
+
+ /**
+ * Determines if the text is being rendered as filled polygons or
+ * wireframes.
+ *
+ * @return if true, uses filled polygons, if false, renderings are
+ * wireframe.
+ */
+ public boolean isFill()
+ {
+ return !this.edgeOnly;
+ }
+
+ /**
+ * Set the flatness to which the glyph's curves will be flattened
+ *
+ * @return
+ */
+ public float getFlatness()
+ {
+ return flatness;
+ }
+
+ /**
+ * Get the current flatness to which the glyph's curves will be flattened
+ *
+ * @return
+ */
+ public void setFlatness(float flatness)
+ {
+ this.flatness = flatness;
+ }
+
+ /**
+ * Sets whether the normals will eb calculated for each face
+ *
+ * @param mode
+ * the mode to render in. Default is flat.
+ */
+ public void setCalcNormals( boolean normals)
+ {
+ this.calcNormals = normals;
+ }
+
+ /**
+ * Gets whether normals are being calculated
+ *
+ * @see setNormal
+ * @return the normal technique for this TextRenderer3D.
+ */
+ public boolean getCalcNormals()
+ {
+ return this.calcNormals;
+ }
+
+ public void draw( String str, float xOff, float yOff, float zOff, float scaleFactor )
+ {
+ gl.glPushAttrib(GL.GL_TRANSFORM_BIT);
+ gl.glMatrixMode(GL.GL_MODELVIEW);
+ gl.glPushMatrix();
+ gl.glEnable( GL.GL_NORMALIZE);
+
+ gl.glScalef(scaleFactor, scaleFactor, scaleFactor);
+ gl.glTranslatef(xOff, yOff, zOff);
+
+ this.draw(str);
+
+ gl.glPopMatrix();
+ gl.glPopAttrib();
+ }
+
+ /**
+ * Renders a string into the specified GL object, starting at the (0,0,0)
+ * point in OpenGL coordinates.
+ *
+ * @param str
+ * the string to render.
+ * @param glu
+ * a GLU instance to use for the text rendering (provided to
+ * prevent continuous re-instantiation of a GLU object)
+ * @param gl
+ * the OpenGL context in which to render the text.
+ */
+ public void draw( String str )
+ {
+ GlyphVector gv = font.createGlyphVector(new FontRenderContext(new AffineTransform(), true, true),
+ new StringCharacterIterator(str));
+ GeneralPath gp = (GeneralPath) gv.getOutline();
+ PathIterator pi = gp.getPathIterator(AffineTransform.getScaleInstance(1.0, -1.0), flatness);
+
+ // dumpShape(gl, gp);
+
+ if (calcNormals)
+ gl.glNormal3f(0, 0, -1.0f);
+
+ tesselateFace(glu, gl, pi, this.edgeOnly, 0.0f);
+
+ if (this.depth != 0.0)
+ {
+ pi = gp.getPathIterator(AffineTransform.getScaleInstance(1.0, -1.0), flatness);
+
+ if (calcNormals)
+ gl.glNormal3f(0, 0, 1.0f);
+
+ tesselateFace(glu, gl, pi, this.edgeOnly, this.depth);
+
+ pi = gp.getPathIterator(AffineTransform.getScaleInstance(1.0, -1.0), flatness);
+
+ // TODO: add diagonal corner/VBO technique
+
+ drawSides(gl, pi, this.edgeOnly, this.depth);
+ }
+ }
+
+ /**
+ * Creates the specified string as a display list. Can subsequently be drawn
+ * by calling "call".
+ *
+ * @param str
+ * @param xOff
+ * @param yOff
+ * @param zOff
+ * @param scaleFactor
+ */
+ public int compile( String str, float xOff, float yOff, float zOff, float scaleFactor )
+ {
+ int index = gl.glGenLists(1);
+ gl.glNewList( index, GL.GL_COMPILE);
+ draw( str, xOff, yOff, zOff, scaleFactor);
+ gl.glEndList();
+
+ listIndex.add(index);
+ return index;
+ }
+
+ /**
+ * Creates the specified string as a display list. Can subsequently be drawn
+ * by calling "call".
+ *
+ * @param str
+ */
+ public int compile( String str )
+ {
+ int index = gl.glGenLists(1);
+ gl.glNewList( index, GL.GL_COMPILE);
+ draw( str );
+ gl.glEndList();
+
+ listIndex.add(index);
+
+ return index;
+ }
+
+ /**
+ * Draws the current compiled string, if any.
+ *
+ */
+ public void call()
+ {
+ if (lastIndex != -1)
+ gl.glCallList(this.lastIndex);
+ }
+
+ /**
+ * Draws the specified compiled string, if any.
+ *
+ */
+ public void call( int index )
+ {
+ gl.glCallList( index );
+ }
+
+ /**
+ * Diposes of the ALL the current compiled shapes, if any.
+ *
+ */
+ public void dispose()
+ {
+ for (Iterator iter = listIndex.iterator(); iter.hasNext();)
+ {
+ int index = (Integer) iter.next();
+ gl.glDeleteLists( index, 1);
+ }
+
+ lastIndex = -1;
+ }
+
+ /**
+ * Diposes of the specified compiled shapes, if it is in the list.
+ * If it is the last-compiled, that index is cleared (set to -1)
+ *
+ */
+ public void dispose( int index )
+ {
+ for (Iterator iter = listIndex.iterator(); iter.hasNext();)
+ {
+ int i = (Integer) iter.next();
+ if (i == index)
+ {
+ gl.glDeleteLists( index, 1);
+ if (index == lastIndex)
+ lastIndex = -1;
+ break;
+ }
+ }
+ }
+
+ /**
+ * Get the bounding box for the supplied string with the current font, etc.
+ *
+ * @param str
+ * @return
+ */
+ public Rectangle2D getBounds( String str )
+ {
+ GlyphVector gv = font.createGlyphVector(new FontRenderContext(new AffineTransform(), true, true),
+ new StringCharacterIterator(str));
+ GeneralPath gp = (GeneralPath) gv.getOutline();
+
+ return gp.getBounds2D();
+ }
+
+ /**
+ * Get the bounding box for the supplied string for the current font and
+ * specified scale factor.
+ *
+ * @param str
+ * @param scaleFactor
+ * @return
+ */
+ public Rectangle2D getBounds( String str, float scaleFactor )
+ {
+ gl.glPushAttrib(GL.GL_TRANSFORM_BIT);
+ gl.glMatrixMode(GL.GL_MODELVIEW);
+ gl.glPushMatrix();
+
+ gl.glScalef(scaleFactor, scaleFactor, scaleFactor);
+
+ GlyphVector gv = font.createGlyphVector(new FontRenderContext(new AffineTransform(), true, true),
+ new StringCharacterIterator(str));
+ GeneralPath gp = (GeneralPath) gv.getOutline();
+
+ Rectangle2D rect = gp.getBounds2D();
+
+ gl.glPopMatrix();
+ gl.glPopAttrib();
+
+ return rect;
+ }
+
+ // construct the sides of each glyph by walking around and extending each vertex
+ // out to the depth of the extrusion
+ private void drawSides(GL gl, PathIterator pi, boolean justBoundary, float depth)
+ {
+ if (justBoundary)
+ gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_LINE);
+ else
+ gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_FILL);
+
+ float[] lastCoord = new float[3];
+ float[] firstCoord = new float[3];
+ float[] coords = new float[6];
+
+ while ( !pi.isDone() )
+ {
+ switch (pi.currentSegment(coords))
+ {
+ case PathIterator.SEG_MOVETO:
+ gl.glBegin(GL.GL_QUADS);
+ lastCoord[0] = coords[0];
+ lastCoord[1] = coords[1];
+ firstCoord[0] = coords[0];
+ firstCoord[1] = coords[1];
+ break;
+ case PathIterator.SEG_LINETO:
+ if (calcNormals)
+ setNormal(gl, lastCoord[0]-coords[0], lastCoord[1]-coords[1], 0.0f,
+ 0.0f, 0.0f, depth);
+
+ lastCoord[2] = 0;
+ gl.glVertex3fv(lastCoord, 0);
+ lastCoord[2] = depth;
+ gl.glVertex3fv(lastCoord, 0);
+ coords[2] = depth;
+ gl.glVertex3fv(coords, 0);
+ coords[2] = 0;
+ gl.glVertex3fv(coords, 0);
+
+ if (calcNormals)
+ {
+ lastCoord[0] = coords[0];
+ lastCoord[1] = coords[1];
+ }
+ break;
+ case PathIterator.SEG_CLOSE:
+ if(calcNormals)
+ setNormal(gl, lastCoord[0]-firstCoord[0], lastCoord[1]-firstCoord[1], 0.0f,
+ 0.0f, 0.0f, depth );
+
+ lastCoord[2] = 0;
+ gl.glVertex3fv(lastCoord, 0);
+ lastCoord[2] = depth;
+ gl.glVertex3fv(lastCoord, 0);
+ firstCoord[2] = depth;
+ gl.glVertex3fv(firstCoord, 0);
+ firstCoord[2] = 0;
+ gl.glVertex3fv(firstCoord, 0);
+ gl.glEnd();
+ break;
+ default:
+ throw new RuntimeException(
+ "PathIterator segment not SEG_MOVETO, SEG_LINETO, SEG_CLOSE; Inappropriate font.");
+ }
+
+ pi.next();
+ }
+ }
+
+ // simple convenience for calculating and setting the normal
+ private void setNormal ( GL gl, float x1, float y1, float z1, float x2, float y2, float z2 )
+ {
+ vecA.set( x1, y1, z1);
+ vecB.set( x2, y2, z2);
+ normal.cross( vecA, vecB );
+ normal.normalize();
+ gl.glNormal3f( normal.x, normal.y, normal.z );
+ }
+
+ // routine that tesselates the current set of glyphs
+ private void tesselateFace(GLU glu, GL gl, PathIterator pi, boolean justBoundary, double tessZ)
+ {
+ GLUtessellatorCallback aCallback = new GLUtesselatorCallbackImpl(gl);
+ GLUtessellator tess = glu.gluNewTess();
+
+ glu.gluTessCallback(tess, GLU.GLU_TESS_BEGIN, aCallback);
+ glu.gluTessCallback(tess, GLU.GLU_TESS_END, aCallback);
+ glu.gluTessCallback(tess, GLU.GLU_TESS_ERROR, aCallback);
+ glu.gluTessCallback(tess, GLU.GLU_TESS_VERTEX, aCallback);
+ glu.gluTessCallback(tess, GLU.GLU_TESS_COMBINE, aCallback);
+
+ glu.gluTessNormal(tess, 0.0, 0.0, -1.0);
+
+ if ( pi.getWindingRule() == PathIterator.WIND_EVEN_ODD)
+ glu.gluTessProperty(tess, GLU.GLU_TESS_WINDING_RULE, GLU.GLU_TESS_WINDING_ODD);
+ else
+ glu.gluTessProperty(tess, GLU.GLU_TESS_WINDING_RULE, GLU.GLU_TESS_WINDING_NONZERO);
+
+ if (justBoundary)
+ glu.gluTessProperty(tess, GLU.GLU_TESS_BOUNDARY_ONLY, GL.GL_TRUE);
+ else
+ glu.gluTessProperty(tess, GLU.GLU_TESS_BOUNDARY_ONLY, GL.GL_FALSE);
+
+ glu.gluTessBeginPolygon(tess, (double[]) null);
+
+
+ while (!pi.isDone())
+ {
+ double[] coords = new double[3];
+ coords[2] = tessZ;
+ switch (pi.currentSegment(coords))
+ {
+ case PathIterator.SEG_MOVETO:
+ glu.gluTessBeginContour(tess);
+ break;
+ case PathIterator.SEG_LINETO:
+ glu.gluTessVertex(tess, coords, 0, coords);
+ break;
+ case PathIterator.SEG_CLOSE:
+ glu.gluTessEndContour(tess);
+ break;
+ }
+
+ pi.next();
+ }
+ glu.gluTessEndPolygon(tess);
+
+ glu.gluDeleteTess(tess);
+ }
+
+ // Private class that implements the required callbacks for the tesselator
+ private class GLUtesselatorCallbackImpl extends javax.media.opengl.glu.GLUtessellatorCallbackAdapter
+ {
+ private GL gl;
+
+ public GLUtesselatorCallbackImpl(GL gl)
+ {
+ this.gl = gl;
+ }
+
+ public void begin(int type)
+ {
+ gl.glBegin(type);
+ }
+
+ public void vertex(java.lang.Object vertexData)
+ {
+ double[] coords = (double[]) vertexData;
+
+ gl.glVertex3dv(coords, 0);
+ }
+
+ public void end()
+ {
+ gl.glEnd();
+ }
+ }
+
+
+ //--------------- diagnostic utilities -----------------------------------------
+ /*
+ private void dumpShape(GL gl, GeneralPath path)
+ {
+ float[] coords = new float[6];
+ int count = 1;
+
+ PathIterator pi = path.getPathIterator(AffineTransform.getScaleInstance(1.0, -1.0));
+ FlatteningPathIterator pif = new FlatteningPathIterator(pi, 0.005);
+
+ while ( !pif.isDone() )
+ {
+ int type = pif.currentSegment(coords);
+ dumpSegment(count++, coords, type );
+
+ pif.next();
+ }
+ }
+
+ private String Segment[] = { "MoveTo", "LineTo", "QuadTo", "CubicTo", "Close" };
+ private String Winding[] = { "EvenOdd", "NonZero" };
+
+ protected void dumpSegment( int num, float[] points, int type )
+ {
+ System.out.print(num + " " + Segment[type]);
+
+ switch(type)
+ {
+ case PathIterator.SEG_MOVETO:
+ case PathIterator.SEG_LINETO:
+ System.out.print(" " + points[0] + "," + points[1] );
+ break;
+
+ case PathIterator.SEG_QUADTO:
+ System.out.print(" " + points[0] + "," + points[1] + " " + points[2] + "," + points[3] );
+ break;
+
+ case PathIterator.SEG_CUBICTO:
+ System.out.print(" " + points[0] + "," + points[1] + " " + points[2] + "," + points[3] + " " + points[4] + "," + points[5] );
+ break;
+ }
+
+ System.out.println();
+ }
+ */
+}