}
GLfloat factor = radius / m_radius;
- glEnable( GL_NORMALIZE );
+// glEnable( GL_NORMALIZE );
glPushMatrix();
glScalef( factor, factor, factor );
draw();
glPopMatrix();
- glDisable( GL_NORMALIZE );
+// glDisable( GL_NORMALIZE );
}
Cylinder::Cylinder()
m_isInitialized = true;
}
-TextPainter::TextPainter()
+CharRenderer::CharRenderer()
{
- m_width = 0;
- m_height = 0;
- m_image = 0;
- m_painter = 0;
- m_fontMetrics = 0;
+ m_texture = 0;
+ m_displayList = 0;
}
-TextPainter::~TextPainter()
+CharRenderer::~CharRenderer()
{
- if( m_image ) delete m_image;
- if( m_painter ) delete m_painter;
- if( m_fontMetrics ) delete m_fontMetrics;
+ if( m_texture ) glDeleteTextures( 1, &m_texture );
+ if( m_displayList ) glDeleteLists( m_displayList, 1 );
}
-
-bool TextPainter::print( QGLWidget *glwidget, int x, int y, const QString &string)
+bool CharRenderer::initialize( QChar c, const QFont &font )
{
- glDisable( GL_LIGHTING );
- glEnable(GL_TEXTURE_2D);
- glEnable( GL_BLEND );
-
- if( ! m_painter )
- {
- m_painter = new QPainter();
- if( ! m_painter ) return false;
- }
-
- if( ! m_fontMetrics )
- {
-
- m_fontMetrics = new QFontMetrics(glwidget->font());
- if( ! m_fontMetrics ) return false;
- }
-
- int new_width = m_fontMetrics->width( string );
- int new_height = m_fontMetrics->height();
+ if( m_displayList ) return true;
+
+ QFontMetrics fontMetrics ( font );
+ m_width = fontMetrics.width( c );
+ m_height = fontMetrics.height();
+ if( m_width == 0 || m_height == 0 ) return false;
+ QImage image( m_width, m_height, QImage::Format_RGB32 );
- if(new_width == 0 || new_height == 0)
+ QPainter painter;
+ painter.begin( &image );
+ painter.setFont( font );
+ painter.setRenderHint( QPainter::TextAntialiasing );
+ painter.setBackground(Qt::black);
+ painter.eraseRect( image.rect() );
+ painter.setPen(Qt::white);
+ painter.drawText ( 0, 0, m_width, m_height, Qt::AlignBottom, c);
+ painter.end();
+
+ GLubyte *bitmap = new GLubyte [m_width * m_height];
+ if( bitmap == 0 ) return false;
+
+ for( int i = 0; i < m_width; i++)
+ for( int j = 0; j < m_height; j++)
{
- return false;
+ bitmap[ i + m_width * j ] =
+ image.pixel( i, m_height - j - 1 ) & 0x000000ff;
}
- if( new_width > m_width || new_height > m_height )
- {
- if( m_image ) delete m_image;
- m_width = ( new_width > m_width ) ? new_width : m_width;
- m_height = ( new_height > m_height ) ? new_height : m_height;
- m_image = new QImage( m_width, m_height, QImage::Format_ARGB32 );
- }
+ glGenTextures( 1, &m_texture );
+ if( m_texture == 0 ) return false;
+
+ glBindTexture(GL_TEXTURE_2D,m_texture) ;
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+ glTexImage2D (
+ GL_TEXTURE_2D,
+ 0,
+ GL_ALPHA,
+ m_width,
+ m_height,
+ 0,
+ GL_ALPHA,
+ GL_UNSIGNED_BYTE,
+ bitmap );
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
- m_painter->begin( m_image );
- m_painter->setFont(glwidget->font());
- m_painter->setRenderHint(QPainter::TextAntialiasing);
- //painter.setBackground(Qt::black);
- m_painter->setBrush(Qt::white);
- m_painter->eraseRect( 0, 0, m_width, m_height );
+ delete [] bitmap;
- //painter.drawText ( 0, 0, s );
- m_painter->drawText ( 0, m_height, string );
- m_painter->end();
+ m_displayList = glGenLists(1);
+ if( m_displayList == 0 ) return false;
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
- glwidget->bindTexture( *m_image );
- glMatrixMode( GL_PROJECTION );
- glPushMatrix();
- glLoadIdentity();
- glOrtho( 0, glwidget->width(), 0, glwidget->height(), -1, 1 );
- glMatrixMode( GL_MODELVIEW );
- glPushMatrix();
- glLoadIdentity();
+ glNewList( m_displayList, GL_COMPILE );
+ glBindTexture( GL_TEXTURE_2D, m_texture );
glBegin(GL_QUADS);
glTexCoord2f( 0, 0);
- glVertex2f( x , y );
+ glVertex2f( 0 , 0 );
glTexCoord2f( 1, 0);
- glVertex2f( x+m_width , y );
+ glVertex2f( m_width , 0 );
glTexCoord2f( 1, 1);
- glVertex2f( x+m_width , y+m_height );
+ glVertex2f( m_width, m_height );
glTexCoord2f( 0, 1);
- glVertex2f( x , y+m_height );
+ glVertex2f( 0 , m_height );
glEnd();
- glDisable( GL_TEXTURE_2D);
- glDisable( GL_BLEND );
- glPopMatrix();
+ glTranslatef( m_width, 0, 0 );
+ glEndList();
+
+ return true;
+}
+
+TextRenderer::TextRenderer()
+{
+ m_glwidget = 0;
+ m_isBetweenBeginAndEnd = false;
+}
+
+TextRenderer::~TextRenderer()
+{
+ QHash<QChar, CharRenderer *>::iterator i = m_charTable.begin();
+ while( i != m_charTable.end() )
+ {
+ delete i.value();
+ i = m_charTable.erase(i);
+ }
+}
+
+void TextRenderer::setup( const QGLWidget *glwidget, const QFont &font )
+{
+ if( m_glwidget ) return;
+ m_glwidget = glwidget;
+ m_font = font;
+}
+
+void TextRenderer::do_begin()
+{
+ m_wasEnabled_LIGHTING = glIsEnabled( GL_LIGHTING );
+ m_wasEnabled_FOG = glIsEnabled( GL_FOG );
+ m_wasEnabled_TEXTURE_2D = glIsEnabled( GL_TEXTURE_2D );
+ m_wasEnabled_BLEND = glIsEnabled( GL_BLEND );
+ m_wasEnabled_DEPTH_TEST = glIsEnabled( GL_DEPTH_TEST );
+ glDisable( GL_LIGHTING );
+ glDisable( GL_FOG );
+ glEnable( GL_TEXTURE_2D );
+ glEnable( GL_BLEND );
+ glDisable( GL_DEPTH_TEST );
+ glMatrixMode( GL_PROJECTION );
+ glPushMatrix();
+ glLoadIdentity();
+ glOrtho( 0, m_glwidget->width(), 0, m_glwidget->height(), -1, 1 );
+ glMatrixMode( GL_MODELVIEW );
+}
+
+void TextRenderer::begin()
+{
+ if( ! m_glwidget ) return;
+ if( m_isBetweenBeginAndEnd ) return;
+ m_isBetweenBeginAndEnd = true;
+ do_begin();
+}
+
+void TextRenderer::do_end()
+{
+ if( ! m_wasEnabled_TEXTURE_2D ) glDisable( GL_TEXTURE_2D);
+ if( ! m_wasEnabled_BLEND ) glDisable( GL_BLEND );
+ if( m_wasEnabled_DEPTH_TEST ) glEnable( GL_DEPTH_TEST );
+ if( m_wasEnabled_LIGHTING ) glEnable( GL_LIGHTING );
+ if( m_wasEnabled_FOG ) glEnable( GL_FOG );
glMatrixMode( GL_PROJECTION );
glPopMatrix();
glMatrixMode( GL_MODELVIEW );
- return true;
+}
+
+void TextRenderer::end()
+{
+ if( m_isBetweenBeginAndEnd ) do_end();
+ m_isBetweenBeginAndEnd = false;
+}
+
+void TextRenderer::print( int x, int y, const QString &string)
+{
+ if( ! m_glwidget ) return;
+ if( string.isEmpty() ) return;
+
+ if( ! m_isBetweenBeginAndEnd ) do_begin();
+
+ glPushMatrix();
+ glLoadIdentity();
+ glTranslatef( x, y, 0 );
+ for(int i = 0; i < string.size(); i++)
+ {
+ if( m_charTable.contains( string[i] ) )
+ m_charTable.value(string[i])->draw();
+ else
+ {
+ CharRenderer *c = new CharRenderer;
+ if( c->initialize( string[i], m_font ) )
+ {
+ m_charTable.insert( string[i], c);
+ c->draw();
+ }
+ else delete c;
+ }
+ }
+ glPopMatrix();
+
+ if( ! m_isBetweenBeginAndEnd ) do_end();
}
#include <QGLWidget>
#include <QPainter>
#include <QImage>
+#include <QChar>
+#include <QHash>
+#include <kdebug.h>
/** USE_DOUBLE_PRECISION: if defined, use doubles instead of floats for
* handling the model's geometric data. This does not seem to impact
}
};
-class TextPainter
+/** This is a helper class for TextRenderer, and should probably never be
+* used directly. See TextRenderer.
+*
+* The CharRenderer class represents a character stored as OpenGL rendering
+* data : a texture object and a display list mapping it on a quad and then
+* translating to the right of it.
+*
+* See the m_charTable member of TextRenderer for an example of use of
+* this class.
+*/
+class CharRenderer
{
protected:
+ /**
+ * The OpenGL texture object
+ */
+ GLuint m_texture;
+
+ /**
+ * The OpenGL display list
+ */
+ GLuint m_displayList;
+
+ /**
+ * Width and height in pixels of the rendered character
+ */
int m_width, m_height;
- QImage *m_image;
- QPainter *m_painter;
- QFontMetrics *m_fontMetrics;
public:
- TextPainter::TextPainter();
- TextPainter::~TextPainter();
- bool TextPainter::print( QGLWidget *glwidget, int x, int y, const QString &string);
+ CharRenderer();
+ ~CharRenderer();
+ bool initialize( QChar c, const QFont &font );
+ inline void draw()
+ {
+ glCallList( m_displayList );
+ }
+};
+
+
+/** This class renders text inside a QGLWidget. It replaces the functionality
+* of QGLWidget::renderText(). The advantages over renderText() include:
+* - supports any font, any character encoding supported by Qt
+* (renderText is 8-bit-only and can only use "OpenGL-compatible" fonts)
+* - does not use any library outside Qt (renderText uses FreeType on X11)
+* - renders characters as textured quads instead of calling glDrawPixels,
+* which does not make much of a difference on MesaGL, but can be a lot
+* faster and safer with other (buggy) OpenGL implementations. It will also
+* allow to add more graphical effects in the future, like rotation,
+* if we ever need that.
+* - the characters are stored as 8bpp Alpha, which takes 4 times less
+* memory than the 32bpp RGBA used by renderText.
+* - the characters are rendered on-the-fly on the first time they appear
+* in a QString being printed. This is achieved using a QHash to test whether
+* a character has already been rendered.
+*
+* Recommended usage:
+* The TextRender class is meant to be used from inside a child class of
+* QGLWidget, say MyGLWidget.
+*
+* In the declaration of MyGLWidget, please declare a TextRenderer member:
+
+class MyGLWidget : public QGLWidget
+{
+ ...
+ TextRenderer m_textRenderer;
+ ...
+};
+
+* Now, in the constructor of MyGLWidget, you please call setup()
+* along these lines:
+
+ QFont f;
+ f.setStyleHint( QFont::SansSerif, QFont::PreferAntialias );
+ m_textRenderer.setup( this, f );
+
+* The setup() method should be called only once, which means you have to choose
+* a font once and for all, in the lifetime of your TextRenderer. Any QFont can
+* be used, the above is just an example. Now, to actually render text, in
+* the MyGLWidget::paintGL() method, you can call
+
+ m_textRenderer.print( x, y, string );
+
+* where x,y are ints and string is any QString. If you want to choose a color,
+* please call glColor3f or glColor4f before calling print(). Of course you can
+* also call qglColor or Color::apply. You can achieve semitransparent text at
+* no additional cost by choosing a semitransparent color.
+*
+* If you wish to do several calls to print(), it will improve performance
+* to enclose them between a call to begin() and a call to end(), like that:
+
+ m_textRenderer.begin();
+ m_textRenderer.print( x1, y1, string1 );
+ m_textRenderer.print( x2, y2, string2 );
+ m_textRenderer.print( x3, y2, string3 );
+ m_textRenderer.end();
+
+* Please make sure, though, that no OpenGL state change occurs between begin()
+* and end(), except the state changes performed by the TextRenderer itself.
+* In other words, please avoid calling glSomething() between begin() and end(),
+* except if you are sure that this call won't perform a relevant state change.
+*
+* The print() method when called alone, or the begin()-print()-end() group,
+* do restore the OpenGL state as they found it, including the matrices.
+*
+* If you experience rendering problems, you can try the following:
+* - disable some OpenGL state bits. For instance, TextRenderer automatically
+* disables fog and lighting during rendering, because it doesn't work
+* correctly with them enabled. There probably are other OpenGL state bits
+* that have to be disabled, so if your program enables some of them, you
+* might have to disable them before rendering text.
+* - if you experience poor font quality, please consider using an antialiased
+* font.
+*
+* @author Benoit Jacob
+*/
+
+class TextRenderer
+{
+ protected:
+ /**
+ * The font used for rendering the chars. This is set
+ * once and for all by setup(). Note that it is stored
+ * by value, so the caller doesn't have to keep it alive.
+ */
+ QFont m_font;
+
+ /**
+ * This hash gives the correspondence table between QChars
+ * (the keys) and the corresponding CharRenderers (the values).
+ * Every time a QChar is being met, either it is found in this
+ * table, in which case it can be directly rendered, or it is not
+ * found, in which case a new CharRenderer is created for it,
+ * and added to this table.
+ */
+ QHash<QChar, CharRenderer*> m_charTable;
+
+ /**
+ * The QGLWidget in which to render. This is set
+ * once and for all by setup().
+ */
+ const QGLWidget *m_glwidget;
+
+ /**
+ * This equals true if begin() has been called, but end() hasn't
+ * since.
+ */
+ GLboolean m_isBetweenBeginAndEnd;
+
+ /**
+ * These members are used to remember the OpenGL state in order
+ * to be able to restore it after rendering. See do_end().
+ */
+ GLboolean m_wasEnabled_LIGHTING;
+ GLboolean m_wasEnabled_TEXTURE_2D;
+ GLboolean m_wasEnabled_FOG;
+ GLboolean m_wasEnabled_BLEND;
+ GLboolean m_wasEnabled_DEPTH_TEST;
+
+ /**
+ * Stores the relevant part of the OpenGL state, and prepares
+ * for rendering
+ */
+ void do_begin();
+
+ /**
+ * Restores the OpenGL state
+ */
+ void do_end();
+
+ public:
+ TextRenderer();
+ ~TextRenderer();
+
+ /**
+ * This should be called only once, before any printing occurs.
+ * @param glwidget The QGLWidget in which to render.
+ * See m_glwidget member.
+ * @param font The QFont to use. See m_font member.
+ */
+ void setup( const QGLWidget *glwidget, const QFont &font );
+
+ /**
+ * Prints text at the position (x,y) in window coordinates
+ * (0,0) is the bottom left corner
+ * @param x the x-coordinate
+ * @param y the y-coordinate
+ * @param string the QString to print
+ */
+ void print( int x, int y, const QString &string);
+
+ /**
+ * Call this before doing multiple calls to print(). This is
+ * not necessary, but will improve performance. Don't forget,
+ * then, to call end() after.
+ */
+ void begin();
+
+ /**
+ * Call this after having called begin() and print().
+ */
+ void end();
};
} // namespace KalziumGL
m_molecule = 0;
m_detail = 0;
m_useFog = false;
- m_textPainter = 0;
m_inZoom = false;
m_inMeasure = false;
+ QFont f;
+ f.setStyleHint( QFont::SansSerif, QFont::PreferAntialias );
+ m_textRenderer.setup( this, f );
+
+
ChooseStylePreset( PRESET_SPHERES_AND_BICOLOR_BONDS );
setMinimumSize( 100,100 );
KalziumGLWidget::~KalziumGLWidget()
{
- if ( m_textPainter) delete m_textPainter;
}
void KalziumGLWidget::initializeGL()
glEnable( GL_DEPTH_TEST );
glDepthFunc( GL_LEQUAL );
glEnable( GL_CULL_FACE );
- glDisable( GL_BLEND );
- //glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
glMatrixMode( GL_MODELVIEW );
glPushMatrix();
glGetDoublev( GL_MODELVIEW_MATRIX, m_RotationMatrix );
glPopMatrix();
- glEnable(GL_LIGHTING);
+ glEnable( GL_RESCALE_NORMAL_EXT );
+
glEnable(GL_LIGHT0);
GLfloat ambientLight[] = { 0.4, 0.4, 0.4, 1.0 };
glLightfv(GL_LIGHT0, GL_SPECULAR, specularLight);
glLightfv(GL_LIGHT0, GL_POSITION, position);
- glEnable(GL_FOG);
GLfloat fogColor[] = { 0.0, 0.0, 0.0, 1.0 };
glFogfv( GL_FOG_COLOR, fogColor );
glFogi( GL_FOG_MODE, GL_LINEAR );
glEnableClientState( GL_NORMAL_ARRAY );
setupObjects();
-
- m_textPainter = new TextPainter;
}
void KalziumGLWidget::paintGL()
{
if( ! m_molecule )
{
+ glColor3f( 0.0, 1.0, 0.6 );
+ glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
+ m_textRenderer.print( 20, height() - 40, i18n("Please load a molecule") );
return;
}
s = QString::number( 1000 * frames /
double( new_time - old_time ),
'f', 1 );
- s += " frames per second" + QString( QChar( 962 ) );
+ s += " frames per second" ;
frames = 0;
old_time = new_time;
}
glColor3f( 1.0, 1.0, 0.0 );
- m_textPainter->print( this, 20, 20, s );
+ m_textRenderer.print( 20, 20, s );
update();
#endif
void KalziumGLWidget::prepareMoleculeData()
{
// translate the molecule so that center has coords 0,0,0
- //m_molecule->Center();
- std::map<std::string, std::string> m;
- m["c"] = "c";
- m_molecule->DoTransformations(&m);
-
+ m_molecule->Center();
// calculate the radius of the molecule
// that is, the maximal distance between an atom of the molecule
Q_OBJECT
protected:
- TextPainter *m_textPainter;
+ TextRenderer m_textRenderer;
/**
* The geometric model of the sphere (used for atoms).