/********************************************************************** BSDYEngine - Dynamic detail engine for "balls and sticks" display Copyright (C) 2007 Donald Ephraim Curtis This file is part of the Avogadro molecular editor project. For more information, see Avogadro is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Avogadro is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. **********************************************************************/ #include "bsdyengine.h" #include #include #include #include #include #include #include #include #include // for OpenGL bits #include #include using namespace std; using namespace Eigen; namespace Avogadro { // our sort function /* Camera *camera = 0; bool sortCameraFarthest( const Primitive* lhs, const Primitive* rhs ) { if ( !lhs ) { if ( rhs ) { return true; } else { return false; } } if ( lhs->type() == Primitive::BondType && rhs->type() == Primitive::BondType ) { if ( camera ) { const Bond *l = static_cast( lhs ); const Bond *r = static_cast( rhs ); const Atom* latom1 = static_cast( l->GetBeginAtom() ); const Atom* latom2 = static_cast( l->GetEndAtom() ); Vector3d lv1( latom1->pos() ); Vector3d lv2( latom2->pos() ); Vector3d ld1 = lv2 - lv1; ld1.normalize(); const Atom* ratom1 = static_cast( r->GetBeginAtom() ); const Atom* ratom2 = static_cast( r->GetEndAtom() ); Vector3d rv1( ratom1->pos() ); Vector3d rv2( ratom2->pos() ); Vector3d rd1 = rv2 - rv1; return camera->distance( ld1 ) >= camera->distance( rd1 ); } } else if ( lhs->type() == Primitive::AtomType && rhs->type() == Primitive::AtomType ) { if ( camera ) { const Atom *l = static_cast( lhs ); const Atom *r = static_cast( rhs ); return camera->distance( l->pos() ) >= camera->distance( r->pos() ); } } return false; } */ BSDYEngine::BSDYEngine(QObject *parent) : Engine(parent), m_settingsWidget(0), m_atomRadiusPercentage(0.3), m_bondRadius(0.1), m_showMulti(2), m_alpha(1.) { } Engine *BSDYEngine::clone() const { BSDYEngine *engine = new BSDYEngine(parent()); engine->setAlias(alias()); engine->m_atomRadiusPercentage = m_atomRadiusPercentage; engine->m_bondRadius = m_bondRadius; engine->m_showMulti = m_showMulti; engine->m_alpha = m_alpha; engine->setEnabled(isEnabled()); return engine; } BSDYEngine::~BSDYEngine() { if ( m_settingsWidget ) { m_settingsWidget->deleteLater(); } } bool BSDYEngine::renderOpaque( PainterDevice *pd ) { // glPushAttrib( GL_TRANSFORM_BIT ); // Render the opaque balls & sticks if m_alpha is 1 if (m_alpha < 0.999) { return true; } Color *map = colorMap(); // possible custom color map if (!map) map = pd->colorMap(); // fall back to global color map // Render the bonds foreach(const Bond *b, bonds()) { Atom* atom1 = pd->molecule()->atomById(b->beginAtomId()); Atom* atom2 = pd->molecule()->atomById(b->endAtomId()); if (!atom1 || !atom2) { qDebug() << "Invalid bond atom IDs" << b->beginAtomId() << atom1 << b->endAtomId() << atom2 << "Bond" << b->id(); continue; } Vector3d v1(*atom1->pos()); Vector3d v2(*atom2->pos()); Vector3d d = v2 - v1; d.normalize(); Vector3d v3((v1 + v2 + d*(radius(atom1) - radius(atom2))) / 2); double shift = 0.15; int order = 1; if (m_showMulti) order = b->order(); map->set(atom1); pd->painter()->setColor( map ); pd->painter()->drawMultiCylinder( v1, v3, m_bondRadius, order, shift ); map->set(atom2); pd->painter()->setColor( map ); pd->painter()->drawMultiCylinder( v3, v2, m_bondRadius, order, shift ); } glDisable( GL_NORMALIZE ); glEnable( GL_RESCALE_NORMAL ); // Render the atoms foreach(const Atom *a, atoms()) { map->set(a); pd->painter()->setColor(map); pd->painter()->drawSphere(a->pos(), radius(a)); } // normalize normal vectors of bonds glDisable( GL_RESCALE_NORMAL ); glEnable( GL_NORMALIZE ); // glPopAttrib(); return true; } bool BSDYEngine::renderTransparent(PainterDevice *pd) { // Render selections when not renderquick Color *map = colorMap(); if (!map) map = pd->colorMap(); glDisable( GL_NORMALIZE ); glEnable( GL_RESCALE_NORMAL ); foreach(const Atom *a, atoms()) { // First render the atom if it is transparent. if (m_alpha < 0.999 && m_alpha > 0.001) { map->set(a); map->setAlpha(m_alpha); pd->painter()->setColor(map); pd->painter()->drawSphere(a->pos(), radius(a)); } // If the atom is selected render the selection if (pd->isSelected(a)) { map->setToSelectionColor(); pd->painter()->setColor(map); pd->painter()->drawSphere(a->pos(), SEL_ATOM_EXTRA_RADIUS + radius(a)); } } glDisable( GL_RESCALE_NORMAL ); glEnable( GL_NORMALIZE ); foreach(const Bond *b, bonds()) { // If the bond is not selected and balls and sticks are opaque do not render it if (!pd->isSelected(b) && m_alpha > 0.999) continue; Atom* atom1 = pd->molecule()->atomById(b->beginAtomId()); Atom* atom2 = pd->molecule()->atomById(b->endAtomId()); if (!atom1 || !atom2) { qDebug() << "Invalid bond atom IDs" << b->beginAtomId() << atom1 << b->endAtomId() << atom2 << "Bond" << b->id(); continue; } Vector3d v1(*atom1->pos()); Vector3d v2(*atom2->pos()); Vector3d d = v2 - v1; d.normalize(); Vector3d v3((v1 + v2 + d*(radius(atom1) - radius(atom2))) / 2); double shift = 0.15; int order = 1; if (m_showMulti) order = b->order(); // The "inner" bond has to be rendered first. if (m_alpha < 0.999 && m_alpha > 0.001) { map->set(atom1); map->setAlpha(m_alpha); pd->painter()->setColor( map ); pd->painter()->drawMultiCylinder( v1, v3, m_bondRadius, order, shift ); map->set(atom2); map->setAlpha(m_alpha); pd->painter()->setColor( map ); pd->painter()->drawMultiCylinder( v3, v2, m_bondRadius, order, shift ); } // Render the selected bond. if (pd->isSelected(b)) { map->setToSelectionColor(); pd->painter()->setColor(map); pd->painter()->drawMultiCylinder( v1, v2, SEL_BOND_EXTRA_RADIUS + m_bondRadius, order, shift ); } } return true; } bool BSDYEngine::renderQuick(PainterDevice *pd) { Color *map = colorMap(); // possible custom color map if (!map) map = pd->colorMap(); // fall back to global color map Color cSel; cSel.setToSelectionColor(); // Render the bonds foreach(Bond *b, bonds()) { Atom* atom1 = pd->molecule()->atomById(b->beginAtomId()); Atom* atom2 = pd->molecule()->atomById(b->endAtomId()); Vector3d v1(*atom1->pos()); Vector3d v2(*atom2->pos()); Vector3d d = v2 - v1; d.normalize(); Vector3d v3((v1 + v2 + d*(radius(atom1)-radius(atom2))) / 2); double shift = 0.15; int order = 1; if (m_showMulti) order = b->order(); if (pd->isSelected(b)) { pd->painter()->setColor(&cSel); pd->painter()->drawMultiCylinder(v1, v2, SEL_BOND_EXTRA_RADIUS + m_bondRadius, order, shift); } else { map->set(atom1); pd->painter()->setColor(map); pd->painter()->drawMultiCylinder(v1, v3, m_bondRadius, order, shift); map->set( atom2 ); pd->painter()->setColor(map); pd->painter()->drawMultiCylinder(v3, v2, m_bondRadius, order, shift); } } glDisable(GL_NORMALIZE); glEnable(GL_RESCALE_NORMAL); // Render the atoms foreach(Atom *a, atoms()) { if (pd->isSelected(a)) { pd->painter()->setColor(&cSel); pd->painter()->drawSphere(a->pos(), SEL_ATOM_EXTRA_RADIUS + radius(a)); } else { map->set(a); pd->painter()->setColor(map); pd->painter()->drawSphere(a->pos(), radius(a)); } } // normalize normal vectors of bonds glDisable(GL_RESCALE_NORMAL); glEnable(GL_NORMALIZE); return true; } bool BSDYEngine::renderPick(PainterDevice *pd) { // Render the bonds foreach(Bond *b, bonds()) { pd->painter()->setName(b); // Add a slight slop factor to make it easier to pick // (e.g., for bond-centric tool) pd->painter()->drawCylinder(*b->beginPos(), *b->endPos(), m_bondRadius+0.05); } // Render the atoms foreach(Atom *a, atoms()) { pd->painter()->setName(a); // add a slight "slop" factor to make it easier to pick // (e.g., during drawing) // heavy atoms get a bit more, hydrogens get a bit less if (a->atomicNumber() > 1) pd->painter()->drawSphere(a->pos(), radius(a) + 0.03); else pd->painter()->drawSphere(a->pos(), radius(a) - 0.06); } return true; } inline double BSDYEngine::radius(const Atom *atom) const { if (atom->atomicNumber()) return OpenBabel::etab.GetVdwRad(atom->atomicNumber()) * m_atomRadiusPercentage; return m_atomRadiusPercentage; } void BSDYEngine::setAtomRadiusPercentage( int percent ) { m_atomRadiusPercentage = 0.1 * percent; emit changed(); } void BSDYEngine::setBondRadius( int value ) { m_bondRadius = value * 0.05; emit changed(); } void BSDYEngine::setShowMulti(int value) { m_showMulti = value; emit changed(); } void BSDYEngine::setOpacity(int value) { m_alpha = 0.05 * value; emit changed(); } double BSDYEngine::radius( const PainterDevice *pd, const Primitive *p ) const { // Atom radius if ( p->type() == Primitive::AtomType ) { if ( pd ) { if ( pd->isSelected( p ) ) return radius( static_cast( p ) ) + SEL_ATOM_EXTRA_RADIUS; } return radius( static_cast( p ) ); } // Bond radius else if ( p->type() == Primitive::BondType ) { if ( pd ) { if ( pd->isSelected( p ) ) return m_bondRadius + SEL_BOND_EXTRA_RADIUS; } return m_bondRadius; } // Something else else return 0.; } double BSDYEngine::transparencyDepth() const { return m_atomRadiusPercentage; } Engine::Layers BSDYEngine::layers() const { return Engine::Opaque | Engine::Transparent; } QWidget *BSDYEngine::settingsWidget() { if (!m_settingsWidget) { m_settingsWidget = new BSDYSettingsWidget(); connect(m_settingsWidget->atomRadiusSlider, SIGNAL(valueChanged(int)), this, SLOT(setAtomRadiusPercentage(int))); connect(m_settingsWidget->bondRadiusSlider, SIGNAL(valueChanged(int)), this, SLOT(setBondRadius(int))); connect(m_settingsWidget->showMulti, SIGNAL(stateChanged(int)), this, SLOT(setShowMulti(int))); connect(m_settingsWidget->opacitySlider, SIGNAL(valueChanged(int)), this, SLOT(setOpacity(int))); connect(m_settingsWidget, SIGNAL(destroyed()), this, SLOT(settingsWidgetDestroyed())); m_settingsWidget->atomRadiusSlider->setValue(10*m_atomRadiusPercentage); m_settingsWidget->bondRadiusSlider->setValue(20*m_bondRadius); m_settingsWidget->showMulti->setCheckState((Qt::CheckState)m_showMulti); m_settingsWidget->opacitySlider->setValue(20*m_alpha); } return m_settingsWidget; } void BSDYEngine::settingsWidgetDestroyed() { qDebug() << "Destroyed Settings Widget"; m_settingsWidget = 0; } void BSDYEngine::writeSettings(QSettings &settings) const { Engine::writeSettings(settings); settings.setValue("atomRadius", 10*m_atomRadiusPercentage); settings.setValue("bondRadius", 20*m_bondRadius); settings.setValue("showMulti", m_showMulti); settings.setValue("opacity", 20*m_alpha); } void BSDYEngine::readSettings(QSettings &settings) { Engine::readSettings(settings); setAtomRadiusPercentage(settings.value("atomRadius", 3).toInt()); setBondRadius(settings.value("bondRadius", 2).toInt()); setShowMulti(settings.value("showMulti", 2).toInt()); setOpacity(settings.value("opacity", 100).toInt()); if (m_settingsWidget) { m_settingsWidget->atomRadiusSlider->setValue(10*m_atomRadiusPercentage); m_settingsWidget->bondRadiusSlider->setValue(20*m_bondRadius); m_settingsWidget->showMulti->setCheckState((Qt::CheckState)m_showMulti); m_settingsWidget->opacitySlider->setValue(20*m_alpha); } } // AVOGADRO_ENGINE_FACTORY(BSDYEngine) } #include "bsdyengine.moc" // This is a static engine... // Q_EXPORT_PLUGIN2( bsdyengine, Avogadro::BSDYEngineFactory )