From ab839feaa36ebe21ce744a021f91a1d71a1586f6 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Beno=C3=AEt=20Jacob?= Date: Mon, 3 Jul 2006 15:31:13 +0000 Subject: [PATCH] Changes in Kalzium's 3D renderer: - Display Lists are back. Enable by #defining USE_DISPLAY_LISTS (currently enabled). Advantage: makes rendering faster Inconvenient: uses more memory TODO: measure how much more memory it uses. Vertex arrays are now automatically converted into (nested) display lists when this option is set. This prevents vertex array data from being redundantly copied into the display list. - Now use OB's GetRGB function to get the color of atoms - Implement our own molecule centering code. Neither OBMol::Center() nor OBMol::Translate() did work for me (but maybe I'm stupid). - reorganization in the VertexArray class ( more to come ) M src/kalziumglwidget.h M src/kalziumglhelperclasses.h M src/kalziumglwidget.cpp M src/kalziumglhelperclasses.cpp svn path=/trunk/KDE/kdeedu/kalzium/src/kalziumglwidget.h; revision=557582 --- kalzium/kalziumglhelperclasses.cpp | 137 ++++++++++++++++++++--------- kalzium/kalziumglhelperclasses.h | 79 +++++++++-------- kalzium/kalziumglwidget.cpp | 116 +++++++++++++----------- kalzium/kalziumglwidget.h | 7 +- 4 files changed, 205 insertions(+), 134 deletions(-) diff --git a/kalzium/kalziumglhelperclasses.cpp b/kalzium/kalziumglhelperclasses.cpp index ba7a135..ebf8261 100644 --- a/kalzium/kalziumglhelperclasses.cpp +++ b/kalzium/kalziumglhelperclasses.cpp @@ -62,7 +62,7 @@ VertexArray::VertexArray() m_vertexBuffer = 0; m_normalBuffer = 0; m_indexBuffer = 0; - m_isInitialized = false; + m_displayList = 0; } VertexArray::~VertexArray() @@ -70,14 +70,14 @@ VertexArray::~VertexArray() if( m_indexBuffer ) delete [] m_indexBuffer; if( m_vertexBuffer ) delete [] m_vertexBuffer; if( m_normalBuffer ) delete [] m_normalBuffer; + if( m_displayList ) + glDeleteLists( m_displayList, 1 ); } bool VertexArray::allocateBuffers() { if( m_vertexCount > 65536 ) return false; - m_isInitialized = false; - if( m_indexBuffer ) { delete [] m_indexBuffer; @@ -100,8 +100,54 @@ bool VertexArray::allocateBuffers() if( ! m_normalBuffer ) return false; m_indexBuffer = new unsigned short[m_indexCount]; if( ! m_indexBuffer ) return false; +} - return true; +void VertexArray::do_draw() +{ + glPushClientAttrib( GL_CLIENT_VERTEX_ARRAY_BIT ); + glEnableClientState( GL_VERTEX_ARRAY ); + glEnableClientState( GL_NORMAL_ARRAY ); + glDisableClientState( GL_COLOR_ARRAY ); + glDisableClientState( GL_EDGE_FLAG_ARRAY ); + glDisableClientState( GL_INDEX_ARRAY ); + glDisableClientState( GL_TEXTURE_COORD_ARRAY ); + glVertexPointer( 3, GL_FLOAT, 0, m_vertexBuffer ); + glNormalPointer( GL_FLOAT, 0, m_normalBuffer ); + if( m_indexCount ) + glDrawElements( m_mode, m_indexCount, + GL_UNSIGNED_SHORT, m_indexBuffer ); + else + glDrawArrays( m_mode, 0, m_vertexCount ); + glPopClientAttrib(); +} + +void VertexArray::compileDisplayListIfNeeded() +{ +#ifdef USE_DISPLAY_LISTS + if( ! m_displayList ) + m_displayList = glGenLists( 1 ); + if( ! m_displayList ) return; + glNewList( m_displayList, GL_COMPILE ); + do_draw(); + glEndList(); + + delete [] m_vertexBuffer; + m_vertexBuffer = 0; + delete [] m_normalBuffer; + m_normalBuffer = 0; + if( m_indexBuffer ) delete [] m_indexBuffer; + m_indexBuffer = 0; +#endif +} + +void VertexArray::initialize() +{ + m_vertexCount = computeVertexCount(); + m_indexCount = computeIndexCount(); + if( m_indexCount < 0 || m_vertexCount < 0 ) return; + allocateBuffers(); + buildBuffers(); + compileDisplayListIfNeeded(); } Sphere::Sphere() @@ -206,15 +252,20 @@ void Sphere::computeVertex( int strip, int column, int row) vertex->z *= m_radius; } - -void Sphere::initialize() +int Sphere::computeVertexCount() { - if( m_detail < 1 ) return; - m_vertexCount = ( 3 * m_detail + 1 ) * ( 5 * m_detail + 1 ); - m_indexCount = (2 * ( 2 * m_detail + 1 ) + 2 ) * 5 * m_detail; + if( m_detail < 1 ) return -1; + return ( 3 * m_detail + 1 ) * ( 5 * m_detail + 1 ); +} - if( ! allocateBuffers() ) return; +int Sphere::computeIndexCount() +{ + if( m_detail < 1 ) return -1; + return (2 * ( 2 * m_detail + 1 ) + 2 ) * 5 * m_detail; +} +void Sphere::buildBuffers() +{ for( int strip = 0; strip < 5; strip++ ) for( int column = 1; column < m_detail; column++ ) for( int row = column; row <= 2 * m_detail + column; row++ ) @@ -246,8 +297,6 @@ void Sphere::initialize() m_indexBuffer[i++] = indexOfVertex( strip, column + 1, 2 * m_detail + column + 1); } - - m_isInitialized = true; } void Sphere::setup( int detail, GLfloat radius ) @@ -293,16 +342,22 @@ void Cylinder::setup( int faces, GLfloat radius ) initialize(); } -void Cylinder::initialize() +int Cylinder::computeVertexCount() { - if( m_faces < 3 ) return; + if( m_faces < 3 ) return -1; + return 2 * m_faces + 2; +} - m_vertexCount = 2 * m_faces + 2; // we will use a redundant vertex array - m_indexCount = 0; // we won't use it. +int Cylinder::computeIndexCount() +{ + if( m_faces < 3 ) return -1; + return 0; +} - if( ! allocateBuffers() ) return; - for( int i = 0; i <= m_faces; i++) +void Cylinder::buildBuffers() +{ + for( int i = 0; i <= m_faces; i++ ) { float angle = 2 * M_PI * i / m_faces; float x = cosf( angle ); @@ -312,20 +367,18 @@ void Cylinder::initialize() m_normalBuffer[ 2 * i ].y = y; m_normalBuffer[ 2 * i ].z = 0.0; - m_vertexBuffer[ 2 * i ].x = x * m_radius ; + m_vertexBuffer[ 2 * i ].x = x * m_radius; m_vertexBuffer[ 2 * i ].y = y * m_radius; m_vertexBuffer[ 2 * i ].z = 1.0; - m_normalBuffer[ 2 * i + 1].x = x; - m_normalBuffer[ 2 * i + 1].y = y; - m_normalBuffer[ 2 * i + 1].z = 0.0; + m_normalBuffer[ 2 * i + 1 ].x = x; + m_normalBuffer[ 2 * i + 1 ].y = y; + m_normalBuffer[ 2 * i + 1 ].z = 0.0; - m_vertexBuffer[ 2 * i + 1].x = x * m_radius; - m_vertexBuffer[ 2 * i + 1].y = y * m_radius ; - m_vertexBuffer[ 2 * i + 1].z = 0.0; + m_vertexBuffer[ 2 * i + 1 ].x = x * m_radius; + m_vertexBuffer[ 2 * i + 1 ].y = y * m_radius; + m_vertexBuffer[ 2 * i + 1 ].z = 0.0; } - - m_isInitialized = true; } CharRenderer::CharRenderer() @@ -353,28 +406,27 @@ bool CharRenderer::initialize( QChar c, const QFont &font ) painter.begin( &image ); painter.setFont( font ); painter.setRenderHint( QPainter::TextAntialiasing ); - painter.setBackground(Qt::black); + painter.setBackground( Qt::black ); painter.eraseRect( image.rect() ); - painter.setPen(Qt::white); - painter.drawText ( 0, 0, m_width, m_height, Qt::AlignBottom, c); + painter.setPen( Qt::blue ); + painter.drawText ( 0, 0, m_width, m_height, Qt::AlignBottom, c ); painter.end(); - GLubyte *bitmap = new GLubyte [m_width * m_height]; + 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++) + for( int j = m_height - 1, n = 0; j >= 0; j-- ) + for( int i = 0; i < m_width; i++, n++ ) { - bitmap[ i + m_width * j ] = - image.pixel( i, m_height - j - 1 ) & 0x000000ff; + bitmap[n] = qBlue( image.pixel( i, j ) ); } glGenTextures( 1, &m_texture ); if( m_texture == 0 ) return false; - glBindTexture(GL_TEXTURE_2D,m_texture) ; - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - glTexImage2D ( + glBindTexture( GL_TEXTURE_2D, m_texture ); + glPixelStorei( GL_UNPACK_ALIGNMENT, 1 ); + glTexImage2D( GL_TEXTURE_2D, 0, GL_ALPHA, @@ -394,7 +446,7 @@ bool CharRenderer::initialize( QChar c, const QFont &font ) glNewList( m_displayList, GL_COMPILE ); glBindTexture( GL_TEXTURE_2D, m_texture ); - glBegin(GL_QUADS); + glBegin( GL_QUADS ); glTexCoord2f( 0, 0); glVertex2f( 0 , 0 ); glTexCoord2f( 1, 0); @@ -406,7 +458,6 @@ bool CharRenderer::initialize( QChar c, const QFont &font ) glEnd(); glTranslatef( m_width, 0, 0 ); glEndList(); - return true; } @@ -478,7 +529,7 @@ void TextRenderer::end() m_isBetweenBeginAndEnd = false; } -void TextRenderer::print( int x, int y, const QString &string) +void TextRenderer::print( int x, int y, const QString &string ) { if( ! m_glwidget ) return; if( string.isEmpty() ) return; @@ -488,10 +539,10 @@ void TextRenderer::print( int x, int y, const QString &string) glPushMatrix(); glLoadIdentity(); glTranslatef( x, y, 0 ); - for(int i = 0; i < string.size(); i++) + for( int i = 0; i < string.size(); i++ ) { if( m_charTable.contains( string[i] ) ) - m_charTable.value(string[i])->draw(); + m_charTable.value( string[i] )->draw(); else { CharRenderer *c = new CharRenderer; diff --git a/kalzium/kalziumglhelperclasses.h b/kalzium/kalziumglhelperclasses.h index 6b8d88a..cf4f94c 100644 --- a/kalzium/kalziumglhelperclasses.h +++ b/kalzium/kalziumglhelperclasses.h @@ -27,12 +27,19 @@ */ #define USE_DOUBLE_PRECISION -/** USE_FPS_COUNTER: if defined, the GL Widgets will show a frames-per-second - * counter. *Use only for testing*: this makes the GL Widget constantly - * redraw, which under normal circumstances is a waste of CPU power. +/** USE_FPS_COUNTER: if defined, the GL Widget will show a frames-per-second + * counter. Use only for testing: this makes the GL Widget constantly + * redraw, which under normal circumstances is a waste of CPU time. */ #define USE_FPS_COUNTER +/** USE_DISPLAY_LISTS: if defined, the whole scene will be stored in + * an OpenGL display list. The vertex arrays will then also be converted to + * display lists, in order to avoid problems. This option improves performance, + * especially when rendering complex models, but increases memory usage. + */ +#define USE_DISPLAY_LISTS + namespace KalziumGLHelpers { @@ -98,7 +105,7 @@ struct Color * returns true if abs( a - b ) <= c * precision * where c = max( abs( a ), abs( b ) ) */ -template static bool approx_equal( T a, T b, T precision ) +template bool approx_equal( T a, T b, T precision ) { T abs_a = FABS( a ); T abs_b = FABS( b ); @@ -210,33 +217,35 @@ template void construct_ortho_basis_given_first_vector( */ class VertexArray { - protected: GLenum m_mode; Vector3 *m_vertexBuffer; Vector3 *m_normalBuffer; - unsigned int m_vertexCount; + int m_vertexCount; unsigned short *m_indexBuffer; - unsigned int m_indexCount; - - bool m_isInitialized; - - virtual void initialize() = 0; + int m_indexCount; + GLuint m_displayList; + + virtual int computeVertexCount() = 0; + virtual int computeIndexCount() = 0; + virtual void buildBuffers() = 0; virtual bool allocateBuffers(); + virtual void compileDisplayListIfNeeded(); + + virtual void initialize(); public: VertexArray(); virtual ~VertexArray(); - virtual inline void select() - { - glVertexPointer( 3, GL_FLOAT, 0, m_vertexBuffer ); - glNormalPointer( GL_FLOAT, 0, m_normalBuffer ); - } + + virtual void do_draw(); virtual inline void draw() { - select(); - glDrawElements( m_mode, m_indexCount, - GL_UNSIGNED_SHORT, m_indexBuffer ); +#ifdef USE_DISPLAY_LISTS + glCallList( m_displayList ); +#else + do_draw(); +#endif } }; @@ -255,8 +264,9 @@ class Sphere : public VertexArray protected: int m_detail; GLfloat m_radius; - - virtual void initialize(); + virtual int computeVertexCount(); + virtual int computeIndexCount(); + virtual void buildBuffers(); public: Sphere(); @@ -276,17 +286,14 @@ class Cylinder : public VertexArray int m_faces; GLfloat m_radius; - virtual void initialize(); + virtual int computeVertexCount(); + virtual int computeIndexCount(); + virtual void buildBuffers(); public: Cylinder(); virtual ~Cylinder() {} virtual void setup( int detail, GLfloat radius ); - virtual inline void draw() - { - select(); - glDrawArrays( m_mode, 0, m_vertexCount ); - } }; /** This is a helper class for TextRenderer, and should probably never be @@ -359,8 +366,7 @@ class MyGLWidget : public QGLWidget }; * @endcode * -* Now, in the constructor of MyGLWidget, you please call setup() -* along these lines: +* Now, in the constructor of MyGLWidget, please call setup() along these lines: * * @code QFont f; @@ -393,10 +399,11 @@ class MyGLWidget : public QGLWidget m_textRenderer.end(); * @endcode * -* 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. +* Please make sure, though, that no relevant 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. @@ -426,9 +433,9 @@ class TextRenderer * 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. + * 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 m_charTable; diff --git a/kalzium/kalziumglwidget.cpp b/kalzium/kalziumglwidget.cpp index 10cfb87..79976d9 100644 --- a/kalzium/kalziumglwidget.cpp +++ b/kalzium/kalziumglwidget.cpp @@ -39,6 +39,8 @@ KalziumGLWidget::KalziumGLWidget( QWidget * parent ) m_isDragging = false; m_molecule = 0; m_detail = 0; + m_displayList = 0; + m_haveToRecompileDisplayList = true; m_useFog = false; m_inZoom = false; m_inMeasure = false; @@ -75,7 +77,7 @@ void KalziumGLWidget::initializeGL() glGetDoublev( GL_MODELVIEW_MATRIX, m_RotationMatrix ); glPopMatrix(); - glEnable( GL_RESCALE_NORMAL_EXT ); + //glEnable( GL_RESCALE_NORMAL_EXT ); glEnable(GL_LIGHT0); @@ -84,10 +86,10 @@ void KalziumGLWidget::initializeGL() GLfloat specularLight[] = { 1.0, 1.0, 1.0, 1.0 }; GLfloat position[] = { 0.8, 0.7, 1.0, 0.0 }; - glLightfv(GL_LIGHT0, GL_AMBIENT, ambientLight); - glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseLight); - glLightfv(GL_LIGHT0, GL_SPECULAR, specularLight); - glLightfv(GL_LIGHT0, GL_POSITION, position); + glLightfv( GL_LIGHT0, GL_AMBIENT, ambientLight ); + glLightfv( GL_LIGHT0, GL_DIFFUSE, diffuseLight ); + glLightfv( GL_LIGHT0, GL_SPECULAR, specularLight ); + glLightfv( GL_LIGHT0, GL_POSITION, position ); GLfloat fogColor[] = { 0.0, 0.0, 0.0, 1.0 }; glFogfv( GL_FOG_COLOR, fogColor ); @@ -101,11 +103,6 @@ void KalziumGLWidget::initializeGL() glEnable( GL_COLOR_SUM_EXT ); glLightModeli( GL_LIGHT_MODEL_COLOR_CONTROL_EXT, GL_SEPARATE_SPECULAR_COLOR_EXT ); - - glEnableClientState( GL_VERTEX_ARRAY ); - glEnableClientState( GL_NORMAL_ARRAY ); - - setupObjects(); } void KalziumGLWidget::paintGL() @@ -113,7 +110,7 @@ void KalziumGLWidget::paintGL() if( ! m_molecule ) { glColor3f( 0.0, 1.0, 0.6 ); - glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); + glClear( GL_COLOR_BUFFER_BIT ); m_textRenderer.print( 20, height() - 40, i18n("Please load a molecule") ); return; } @@ -143,6 +140,14 @@ void KalziumGLWidget::paintGL() } else glDisable( GL_FOG ); +#ifdef USE_DISPLAY_LISTS + if( m_haveToRecompileDisplayList ) + { + if( ! m_displayList ) m_displayList = glGenLists( 1 ); + if( ! m_displayList ) return; + glNewList( m_displayList, GL_COMPILE ); +#endif + // prepare for rendering the spheres if( m_atomStyle == ATOM_SPHERE ) { @@ -238,6 +243,12 @@ void KalziumGLWidget::paintGL() } } } +#ifdef USE_DISPLAY_LISTS + glEndList(); + m_haveToRecompileDisplayList = false; + } + glCallList( m_displayList ); +#endif // now, paint a semitransparent sphere around the selected atoms if( m_selectedAtoms.count() > 0 )//there are items selected @@ -266,6 +277,7 @@ void KalziumGLWidget::paintGL() glDisable( GL_BLEND ); } } + #ifdef USE_FPS_COUNTER QTime t; @@ -291,7 +303,7 @@ void KalziumGLWidget::paintGL() s = QString::number( 1000 * frames / double( new_time - old_time ), 'f', 1 ); - s += " frames per second" ; + s += " FPS"; frames = 0; old_time = new_time; } @@ -346,6 +358,9 @@ void KalziumGLWidget::mouseMoveEvent( QMouseEvent * event ) void KalziumGLWidget::rotate( ) { +// OK, let's momentarily disable that until I get it working (Benoit) + +/* kDebug() << "KalziumGLWidget::rotate()" << endl; //TODO at this place we need a nice way to rotate //based on certain values. For example, we could use two @@ -365,6 +380,7 @@ void KalziumGLWidget::rotate( ) glGetDoublev( GL_MODELVIEW_MATRIX, m_RotationMatrix ); glPopMatrix(); updateGL(); +*/ } void KalziumGLWidget::setupObjects() @@ -481,6 +497,7 @@ void KalziumGLWidget::slotSetMolecule( OpenBabel::OBMol* molecule ) { if ( !molecule ) return; m_molecule = molecule; + m_haveToRecompileDisplayList = true; prepareMoleculeData(); setupObjects(); updateGL(); @@ -521,14 +538,42 @@ void KalziumGLWidget::ChooseStylePreset( StylePreset stylePreset ) m_bondRadiusCoeff = 0.0; break; } +} + + +void KalziumGLWidget::slotChooseStylePreset( int stylePreset ) +{ + ChooseStylePreset( (StylePreset) stylePreset ); + m_haveToRecompileDisplayList = true; setupObjects(); updateGL(); } void KalziumGLWidget::prepareMoleculeData() { - // translate the molecule so that center has coords 0,0,0 - m_molecule->Center(); + //Center the molecule + //normally this is done by OBMol::Center() + //but it doesn't seem to work for me + //perhaps I'm stupid (Benoit 03/07/06) + + //first, calculate the coords of the center of the molecule + vector3 center( 0.0, 0.0, 0.0); + int number_of_atoms = 0; + FOR_ATOMS_OF_MOL( a, m_molecule ) + { + center += a->GetVector(); + number_of_atoms++; + } + center /= number_of_atoms; + + //now, translate the molecule so that it gets centered. + //unfortunately OBMol::Translate doesn't seem to work for me + //(Benoit 03/07/06) + FOR_ATOMS_OF_MOL( a, m_molecule ) + { + vector3 new_vector = a->GetVector() - center; + a->SetVector( new_vector ); + } // calculate the radius of the molecule // that is, the maximal distance between an atom of the molecule @@ -576,45 +621,12 @@ void KalziumGLWidget::slotSetDetail( int detail ) Color& KalziumGLWidget::getAtomColor( OpenBabel::OBAtom* atom ) { + // thanks to Geoffrey Hutchison from OpenBabel for + // this simplified getAtomColor method static Color c; - - if ( atom->IsHydrogen() ) - {//white - c.m_red = 1.0; - c.m_green = 1.0; - c.m_blue = 1.0; - } - else if ( atom->IsCarbon() ) - {//almost black - c.m_red = 0.25; - c.m_green = 0.25; - c.m_blue = 0.25; - } - else if ( atom->IsOxygen() ) - {//red - c.m_red = 1.0; - c.m_green = 0.0; - c.m_blue = 0.0; - } - else if ( atom->IsNitrogen() ) - { - c.m_red = 1.0; - c.m_green = 0.9; - c.m_blue = 0.5; - } - else if ( atom->IsSulfur() ) - {//yellow - c.m_red = 1.0; - c.m_green = 1.0; - c.m_blue = 0.0; - } - else - { - c.m_red = 0.5; - c.m_green = 0.5; - c.m_blue = 0.5; - } - + c.m_red = etab.GetRGB(atom->GetAtomicNum())[0]; + c.m_green = etab.GetRGB(atom->GetAtomicNum())[1]; + c.m_blue = etab.GetRGB(atom->GetAtomicNum())[2]; return c; } diff --git a/kalzium/kalziumglwidget.h b/kalzium/kalziumglwidget.h index ec135ee..87bda6e 100644 --- a/kalzium/kalziumglwidget.h +++ b/kalzium/kalziumglwidget.h @@ -32,6 +32,9 @@ class KalziumGLWidget : public QGLWidget Q_OBJECT protected: + GLuint m_displayList; + bool m_haveToRecompileDisplayList; + TextRenderer m_textRenderer; /** @@ -198,9 +201,7 @@ class KalziumGLWidget : public QGLWidget * Chooses the style of rendering among some presets * @param stylePreset the wanted style preset */ - void slotChooseStylePreset( int stylePreset ){ - ChooseStylePreset( (StylePreset) stylePreset ); - } + void slotChooseStylePreset( int stylePreset ); /** * The atoms @p atoms was selected by the user -- 2.47.3