Skip to content

Commit

Permalink
Allow semi-transparent ball-and-sticks and vdw rendering (#1783)
Browse files Browse the repository at this point in the history
* Allow semi-transparent ball-and-sticks and vdw rendering

* Handle cloning settings for layer-specific options

* Ensure VdW settings can be changed for each layer

Signed-off-by: Geoff Hutchison <[email protected]>

---------

Signed-off-by: Geoff Hutchison <[email protected]>
  • Loading branch information
ghutchis authored Nov 15, 2024
1 parent 08b00ad commit ad91707
Show file tree
Hide file tree
Showing 16 changed files with 410 additions and 276 deletions.
2 changes: 2 additions & 0 deletions avogadro/core/layermanager.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ struct LayerData

virtual ~LayerData() = default;

virtual LayerData* clone() { return new LayerData(serialize()); };

/** get the saved data */
std::string getSave() const { return m_save; }

Expand Down
10 changes: 7 additions & 3 deletions avogadro/qtgui/pluginlayermanager.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

#include <avogadro/core/layermanager.h>
#include <cassert>
#include <iostream>

namespace Avogadro {
namespace QtGui {
Expand Down Expand Up @@ -79,22 +80,25 @@ class AVOGADROQTGUI_EXPORT PluginLayerManager : protected Core::LayerManager
/** @return custom data T derived from LayerData. if @p layer is equal to
* MaxIndex returns activeLayer */
template <typename T>
T& getSetting(size_t layer = MaxIndex)
T* getSetting(size_t layer = MaxIndex)
{
auto info = m_molToInfo[m_activeMolecule];

if (layer == MaxIndex) {
layer = info->layer.activeLayer();
}

assert(layer <= info->layer.maxLayer());
if (info->settings.find(m_name) == info->settings.end()) {
info->settings[m_name] = Core::Array<Core::LayerData*>();
}

// do we need to create new layers in the array?
while (info->settings[m_name].size() < layer + 1) {
info->settings[m_name].push_back(new T());
}
auto result = static_cast<T*>(info->settings[m_name][layer]);
return *result;
auto* result = static_cast<T*>(info->settings[m_name][layer]);
return result;
}

private:
Expand Down
10 changes: 6 additions & 4 deletions avogadro/qtgui/rwlayermanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ class AddLayerCommand : public QUndoCommand
auto value = names.second[activeLayer];
m_settings[names.first] = value;
}

}

void redo() override
Expand All @@ -59,7 +58,9 @@ class AddLayerCommand : public QUndoCommand
m_moleculeInfo->enable[enable.first].push_back(enable.second);
}
for (const auto& settings : m_settings) {
m_moleculeInfo->settings[settings.first].push_back(settings.second);
// create newSettings pointer with the same type as settings.second
auto* newSettings = settings.second->clone();
m_moleculeInfo->settings[settings.first].push_back(newSettings);
}

m_moleculeInfo->layer.addLayer();
Expand Down Expand Up @@ -130,7 +131,8 @@ class RemoveLayerCommand : public QUndoCommand
RemoveLayerCommand(shared_ptr<MoleculeInfo> mol, size_t layer)
: QUndoCommand(QObject::tr("Modify Layers")), m_moleculeInfo(mol),
m_layer(layer)
{}
{
}

void redo() override
{
Expand Down Expand Up @@ -285,4 +287,4 @@ Array<std::pair<size_t, string>> RWLayerManager::activeMoleculeNames() const
return result;
}

} // namespace Avogadro
} // namespace Avogadro::QtGui
169 changes: 132 additions & 37 deletions avogadro/qtplugins/ballandstick/ballandstick.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
#include <QtWidgets/QVBoxLayout>
#include <QtWidgets/QWidget>

#include <iostream>

namespace Avogadro::QtPlugins {

using Core::Elements;
Expand All @@ -37,15 +39,24 @@ struct LayerBallAndStick : Core::LayerData
bool showHydrogens;
float atomScale;
float bondRadius;
float opacity;

LayerBallAndStick()
{
widget = nullptr;
QSettings settings;

atomScale = settings.value("ballandstick/atomScale", 0.3).toDouble();
bondRadius = settings.value("ballandstick/bondRadius", 0.1).toDouble();
multiBonds = settings.value("ballandstick/multiBonds", true).toBool();
showHydrogens = settings.value("ballandstick/showHydrogens", true).toBool();
opacity = settings.value("ballandstick/opacity", 1.0).toDouble();
}

LayerBallAndStick(std::string settings)
{
widget = nullptr;
deserialize(settings);
}

~LayerBallAndStick() override
Expand All @@ -57,7 +68,8 @@ struct LayerBallAndStick : Core::LayerData
std::string serialize() final
{
return boolToString(multiBonds) + " " + boolToString(showHydrogens) + " " +
std::to_string(atomScale) + " " + std::to_string(bondRadius);
std::to_string(atomScale) + " " + std::to_string(bondRadius) + " " +
std::to_string(opacity);
}

void deserialize(std::string text) final
Expand All @@ -72,8 +84,13 @@ struct LayerBallAndStick : Core::LayerData
atomScale = std::stof(aux);
ss >> aux;
bondRadius = std::stof(aux);
ss >> aux;
if (!aux.empty())
opacity = std::stof(aux); // backwards compatibility
}

LayerData* clone() final { return new LayerBallAndStick(serialize()); }

void setupWidget(BallAndStick* slot)
{
if (!widget) {
Expand All @@ -98,6 +115,16 @@ struct LayerBallAndStick : Core::LayerData
QObject::connect(bondRadiusSlider, &QSlider::valueChanged, slot,
&BallAndStick::bondRadiusChanged);
f->addRow(QObject::tr("Bond scale"), bondRadiusSlider);

auto* opacitySlider = new QSlider(Qt::Horizontal);
opacitySlider->setMinimum(0);
opacitySlider->setMaximum(100);
opacitySlider->setTickInterval(1);
opacitySlider->setValue(static_cast<int>(opacity * 100));
QObject::connect(opacitySlider, &QSlider::valueChanged, slot,
&BallAndStick::opacityChanged);
f->addRow(QObject::tr("Opacity"), opacitySlider);

v->addLayout(f);

auto* check = new QCheckBox(QObject::tr("Show multiple bonds"));
Expand Down Expand Up @@ -134,11 +161,22 @@ void BallAndStick::process(const QtGui::Molecule& molecule,
auto* geometry = new GeometryNode;
node.addChild(geometry);
auto* spheres = new SphereGeometry;
auto selectedSpheres = new SphereGeometry;
selectedSpheres->setOpacity(0.42);
spheres->identifier().molecule = reinterpret_cast<const void*>(&molecule);
spheres->identifier().type = Rendering::AtomType;
geometry->addDrawable(spheres);

// if we have to draw any translucent spheres, we need to add a separate
// geometry node for them
auto translucentSpheres = new SphereGeometry;
translucentSpheres->setRenderPass(Rendering::TranslucentPass);
translucentSpheres->identifier().molecule =
reinterpret_cast<const void*>(&molecule);
translucentSpheres->identifier().type = Rendering::AtomType;
geometry->addDrawable(translucentSpheres);

// for the selected atoms
auto selectedSpheres = new SphereGeometry;
selectedSpheres->setOpacity(0.42);
geometry->addDrawable(selectedSpheres);

for (Index i = 0; i < molecule.atomCount(); ++i) {
Expand All @@ -147,17 +185,26 @@ void BallAndStick::process(const QtGui::Molecule& molecule,
continue;
}
unsigned char atomicNumber = atom.atomicNumber();
auto& interface = m_layerManager.getSetting<LayerBallAndStick>(

auto* interface = m_layerManager.getSetting<LayerBallAndStick>(
m_layerManager.getLayerID(i));
if (atomicNumber == 1 && !interface.showHydrogens)
if (atomicNumber == 1 && !interface->showHydrogens)
continue;

Vector3ub color = atom.color();
auto radius = static_cast<float>(Elements::radiusVDW(atomicNumber));
float scale = interface.atomScale;
spheres->addSphere(atom.position3d().cast<float>(), color, radius * scale,
i);
float scale = interface->atomScale;

if (interface->opacity < 1.0f) {
translucentSpheres->addSphere(atom.position3d().cast<float>(), color,
radius * scale, i);
translucentSpheres->setOpacity(interface->opacity);
} else
spheres->addSphere(atom.position3d().cast<float>(), color, radius * scale,
i);

if (atom.selected()) {
// add the selected indicator
color = Vector3ub(0, 0, 255);
radius *= 1.2;
selectedSpheres->addSphere(atom.position3d().cast<float>(), color,
Expand All @@ -169,25 +216,40 @@ void BallAndStick::process(const QtGui::Molecule& molecule,
cylinders->identifier().molecule = &molecule;
cylinders->identifier().type = Rendering::BondType;
geometry->addDrawable(cylinders);

auto* translucentBonds = new CylinderGeometry;
translucentBonds->setRenderPass(Rendering::TranslucentPass);
translucentBonds->identifier().molecule = &molecule;
translucentBonds->identifier().type = Rendering::BondType;
float opacity = 1.0f; // for any translucent bonds
geometry->addDrawable(translucentBonds);

for (Index i = 0; i < molecule.bondCount(); ++i) {
Core::Bond bond = molecule.bond(i);
if (!m_layerManager.bondEnabled(bond.atom1().index(),
bond.atom2().index())) {
continue;
}

auto& interface1 = m_layerManager.getSetting<LayerBallAndStick>(
auto* interface1 = m_layerManager.getSetting<LayerBallAndStick>(
m_layerManager.getLayerID(bond.atom1().index()));
auto& interface2 = m_layerManager.getSetting<LayerBallAndStick>(
auto* interface2 = m_layerManager.getSetting<LayerBallAndStick>(
m_layerManager.getLayerID(bond.atom2().index()));

if (!interface1.showHydrogens && !interface2.showHydrogens &&
if (!interface1->showHydrogens && !interface2->showHydrogens &&
(bond.atom1().atomicNumber() == 1 ||
bond.atom2().atomicNumber() == 1)) {
continue;
}

float bondRadius = (interface1.bondRadius + interface2.bondRadius) * 0.5f;
bool doOpaque = true;
if (interface1->opacity < 1.0f || interface2->opacity < 1.0f) {
opacity = std::min(interface1->opacity, interface2->opacity);
translucentBonds->setOpacity(opacity);
doOpaque = false;
}

float bondRadius = (interface1->bondRadius + interface2->bondRadius) * 0.5f;

Vector3f pos1 = bond.atom1().position3d().cast<float>();
Vector3f pos2 = bond.atom2().position3d().cast<float>();
Expand All @@ -197,53 +259,86 @@ void BallAndStick::process(const QtGui::Molecule& molecule,
float bondLength = bondVector.norm();
bondVector /= bondLength;

switch (interface1.multiBonds || interface2.multiBonds ? bond.order() : 1) {
switch (interface1->multiBonds || interface2->multiBonds ? bond.order()
: 1) {
case 3: {
Vector3f delta = bondVector.unitOrthogonal();
// Rotate 45 degrees around the bond vector.
Eigen::Quaternionf q;
q = Eigen::AngleAxisf(45.0f * DEG_TO_RAD_F, bondVector);
delta = q * delta * 2.0f * bondRadius;
cylinders->addCylinder(pos1 + delta, pos2 + delta, bondRadius * 1.15,
color1, color2, i);
cylinders->addCylinder(pos1 - delta, pos2 - delta, bondRadius * 1.15,
color1, color2, i);
if (doOpaque) {
cylinders->addCylinder(pos1 + delta, pos2 + delta, bondRadius * 1.15,
color1, color2, i);
cylinders->addCylinder(pos1 - delta, pos2 - delta, bondRadius * 1.15,
color1, color2, i);
} else {
translucentBonds->addCylinder(pos1 + delta, pos2 + delta,
bondRadius * 1.15, color1, color2, i);
translucentBonds->addCylinder(pos1 - delta, pos2 - delta,
bondRadius * 1.15, color1, color2, i);
}
// This relies upon the single bond case below for the third cylinder.
[[fallthrough]];
}
default:
case 1:
cylinders->addCylinder(pos1, pos2, m_bondRadius, color1, color2, i);
if (doOpaque) {
cylinders->addCylinder(pos1, pos2, m_bondRadius, color1, color2, i);
} else {
translucentBonds->addCylinder(pos1, pos2, m_bondRadius, color1,
color2, i);
}
break;
case 2: {
Vector3f delta = bondVector.unitOrthogonal();
// Rotate 45 degrees around the bond vector.
Eigen::Quaternionf q;
q = Eigen::AngleAxisf(45.0f * DEG_TO_RAD_F, bondVector);
delta = q * delta * bondRadius;
cylinders->addCylinder(pos1 + delta, pos2 + delta, bondRadius * 1.3,
color1, color2, i);
cylinders->addCylinder(pos1 - delta, pos2 - delta, bondRadius * 1.3,
color1, color2, i);
if (doOpaque) {
cylinders->addCylinder(pos1 + delta, pos2 + delta, bondRadius * 1.3,
color1, color2, i);
cylinders->addCylinder(pos1 - delta, pos2 - delta, bondRadius * 1.3,
color1, color2, i);
} else {
translucentBonds->addCylinder(pos1 + delta, pos2 + delta,
bondRadius * 1.3, color1, color2, i);
translucentBonds->addCylinder(pos1 - delta, pos2 - delta,
bondRadius * 1.3, color1, color2, i);
}
}
}
}
}

QWidget* BallAndStick::setupWidget()
{
auto& interface = m_layerManager.getSetting<LayerBallAndStick>();
interface.setupWidget(this);
return interface.widget;
auto* interface = m_layerManager.getSetting<LayerBallAndStick>();
interface->setupWidget(this);
return interface->widget;
}

void BallAndStick::opacityChanged(int opacity)
{
m_opacity = static_cast<float>(opacity) / 100.0f;
auto* interface = m_layerManager.getSetting<LayerBallAndStick>();
if (m_opacity != interface->opacity) {
interface->opacity = m_opacity;
emit drawablesChanged();
}

QSettings settings;
settings.setValue("ballandstick/opacity", m_opacity);
}

void BallAndStick::atomRadiusChanged(int value)
{
m_atomScale = static_cast<float>(value) / 10.0f;

auto& interface = m_layerManager.getSetting<LayerBallAndStick>();
if (m_atomScale != interface.atomScale) {
interface.atomScale = m_atomScale;
auto* interface = m_layerManager.getSetting<LayerBallAndStick>();
if (m_atomScale != interface->atomScale) {
interface->atomScale = m_atomScale;
emit drawablesChanged();
}

Expand All @@ -255,9 +350,9 @@ void BallAndStick::bondRadiusChanged(int value)
{
m_bondRadius = static_cast<float>(value) / 10.0f;

auto& interface = m_layerManager.getSetting<LayerBallAndStick>();
if (m_bondRadius != interface.bondRadius) {
interface.bondRadius = m_bondRadius;
auto* interface = m_layerManager.getSetting<LayerBallAndStick>();
if (m_bondRadius != interface->bondRadius) {
interface->bondRadius = m_bondRadius;
emit drawablesChanged();
}

Expand All @@ -267,9 +362,9 @@ void BallAndStick::bondRadiusChanged(int value)

void BallAndStick::multiBonds(bool show)
{
auto& interface = m_layerManager.getSetting<LayerBallAndStick>();
if (show != interface.multiBonds) {
interface.multiBonds = show;
auto* interface = m_layerManager.getSetting<LayerBallAndStick>();
if (show != interface->multiBonds) {
interface->multiBonds = show;
emit drawablesChanged();
}
QSettings settings;
Expand All @@ -278,9 +373,9 @@ void BallAndStick::multiBonds(bool show)

void BallAndStick::showHydrogens(bool show)
{
auto& interface = m_layerManager.getSetting<LayerBallAndStick>();
if (show != interface.showHydrogens) {
interface.showHydrogens = show;
auto* interface = m_layerManager.getSetting<LayerBallAndStick>();
if (show != interface->showHydrogens) {
interface->showHydrogens = show;
emit drawablesChanged();
}
QSettings settings;
Expand Down
Loading

0 comments on commit ad91707

Please sign in to comment.