diff --git a/.gitignore b/.gitignore index 57a7254..4e08ed9 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ Desktop*/ Debug*/ Release*/ Makefile +.cache/ .DS_Store resources/basis_sets resources/tonto diff --git a/shaders/billboard.frag b/shaders/billboard.frag index 02f08ff..2f9bb0f 100644 --- a/shaders/billboard.frag +++ b/shaders/billboard.frag @@ -1,29 +1,30 @@ #version 330 -in vec2 v_mapping; -in vec3 v_cameraPos; -out vec4 outputColor; +in vec2 v_texcoord; +out vec4 fragColor; -uniform float u_screenGamma; -uniform mat4 u_projectionMat; uniform sampler2D u_texture; -uniform float u_textSDFBuffer; -uniform float u_textSDFSmoothing; -uniform float u_textSDFOutline; uniform vec3 u_textColor; uniform vec3 u_textOutlineColor; - -vec3 gammaCorrection(vec3 color, float gamma) { - return pow(color, vec3(1.0/gamma)); -} +uniform float u_textSDFSmoothing; +uniform float u_textSDFBuffer; +uniform float u_textSDFOutline; void main() { - highp vec2 tx = clamp(v_mapping, 0, 1); - float smoothing = u_textSDFSmoothing; - float buf = u_textSDFBuffer; - float distance = texture(u_texture, tx).r; - float border = smoothstep(buf + u_textSDFOutline - smoothing, buf + u_textSDFOutline + smoothing, distance); - float alpha = smoothstep(buf - smoothing, buf + smoothing, distance); - outputColor = vec4(mix(u_textOutlineColor, u_textColor, border), 1.) * alpha; + float distance = texture(u_texture, v_texcoord).r; + distance -= u_textSDFBuffer; + float smoothing = u_textSDFSmoothing * fwidth(distance); + float textAlpha = smoothstep(0.5 + smoothing, 0.5 - smoothing, distance); + float outlineAlpha = smoothstep(0.5 + u_textSDFOutline + smoothing, + 0.5 + u_textSDFOutline - smoothing, + distance); + vec3 color = mix(u_textOutlineColor, u_textColor, textAlpha); + float alpha = max(textAlpha, outlineAlpha); + fragColor = vec4(color, alpha); + + // Discard nearly transparent fragments + if (alpha < 0.01) { + discard; + } } diff --git a/shaders/billboard.vert b/shaders/billboard.vert index f2e188f..dfead58 100644 --- a/shaders/billboard.vert +++ b/shaders/billboard.vert @@ -1,4 +1,5 @@ #version 330 + layout(location = 0) in vec3 position; layout(location = 1) in vec2 dimensions; layout(location = 2) in float alpha; @@ -7,16 +8,20 @@ layout(location = 3) in vec2 texcoord; uniform mat4 u_projectionMat; uniform mat4 u_viewMat; uniform float u_scale; -out vec3 v_cameraPos; -out vec2 v_mapping; +out vec2 v_texcoord; +out float v_alpha; void main() { - v_mapping = texcoord; - v_cameraPos = vec3(u_viewMat * vec4(position, 1.0)); - v_cameraPos.z += 0.5 * u_scale; - vec4 cameraCornerPos = vec4(v_cameraPos, 1.0); + v_texcoord = texcoord; + v_alpha = alpha; + + vec3 cameraPos = vec3(u_viewMat * vec4(position, 1.0)); + cameraPos.z += 0.5 * u_scale; + + vec4 cameraCornerPos = vec4(cameraPos, 1.0); cameraCornerPos.xy += (0.002 * texcoord * dimensions) * clamp(u_scale * u_scale, 0.5, 10.0); + gl_Position = u_projectionMat * cameraCornerPos; } diff --git a/src/core/pair_energy_results.cpp b/src/core/pair_energy_results.cpp index 6f2cea9..8c0358f 100644 --- a/src/core/pair_energy_results.cpp +++ b/src/core/pair_energy_results.cpp @@ -72,17 +72,17 @@ QStringList PairInteractions::interactionComponents(const QString &model) { void PairInteractions::add(PairInteraction *result) { if (!result) return; - qDebug() << "Adding interaction" << result; QString model = result->interactionModel(); m_pairInteractions[model].insert({result->pairIndex(), result}); - double v = 0.0; + impl::ValueRange currentRange; { - const auto kv = m_maxNearestDistance.find(model); - if (kv != m_maxNearestDistance.end()) { - v = kv->second; + const auto kv = m_distanceRange.find(model); + if (kv != m_distanceRange.end()) { + currentRange = kv->second; } } - m_maxNearestDistance[model] = qMax(v, result->nearestAtomDistance()); + double d = result->nearestAtomDistance(); + m_distanceRange[model] = currentRange.update(d); emit interactionAdded(); } @@ -159,9 +159,9 @@ PairInteractions::getInteractionsMatchingFragments( QMap result; for (const auto &model : models) { double maxDistance = 0.0; - const auto kv = m_maxNearestDistance.find(model); - if (kv != m_maxNearestDistance.end()) - maxDistance = kv->second; + const auto kv = m_distanceRange.find(model); + if (kv != m_distanceRange.end()) + maxDistance = kv->second.maxValue; QList l; for (const auto &dimer : dimers) { @@ -191,6 +191,14 @@ PairInteraction *PairInteractions::getInteraction(const QString &model, return result->second; } +void PairInteractions::resetCounts() { + const auto models = interactionModels(); + for (auto &[model, interactions]: m_pairInteractions) { + for(auto &[idx, interaction]: interactions) { + interaction->setCount(0); + } + } +} bool PairInteractions::haveInteractions(const QString &model) const { return getCount(model) > 0; diff --git a/src/core/pair_energy_results.h b/src/core/pair_energy_results.h index c0ead3a..50d7c18 100644 --- a/src/core/pair_energy_results.h +++ b/src/core/pair_energy_results.h @@ -27,18 +27,35 @@ class PairInteraction : public QObject { inline const auto &color() const { return m_color; } inline void setColor(QColor color) { m_color = color; } + inline int count() const { return m_count; } + inline void setCount(int c) { m_count = c; } void setParameters(const pair_energy::Parameters &); const pair_energy::Parameters ¶meters() const; inline const auto &pairIndex() const { return m_parameters.fragmentDimer.index; } private: + int m_count{0}; QColor m_color{Qt::blue}; QString m_interactionModel; EnergyComponents m_components; pair_energy::Parameters m_parameters; }; +namespace impl { + struct ValueRange { + double minValue{std::numeric_limits::max()}; + double maxValue{std::numeric_limits::min()}; + + inline ValueRange merge(ValueRange rhs) const { + return ValueRange{qMin(minValue, rhs.minValue), qMax(maxValue, rhs.maxValue)}; + } + inline ValueRange update(double v) const { + return ValueRange{qMin(minValue, v), qMax(maxValue, v)}; + } + }; +} + class PairInteractions : public QObject { Q_OBJECT public: @@ -51,6 +68,8 @@ class PairInteractions : public QObject { void add(PairInteraction *result); void remove(PairInteraction *result); + void resetCounts(); + QMap getInteractionsMatchingFragments(const std::vector &frag); PairInteraction *getInteraction(const QString &model, @@ -73,8 +92,10 @@ class PairInteractions : public QObject { void interactionRemoved(); private: + + ModelInteractions m_pairInteractions; - ankerl::unordered_dense::map m_maxNearestDistance; + ankerl::unordered_dense::map m_distanceRange; bool m_haveDimerMap{false}; occ::crystal::DimerMappingTable m_dimerMappingTable; }; diff --git a/src/dialogs/interactioninfodocument.cpp b/src/dialogs/interactioninfodocument.cpp index f77f7ac..30ef397 100644 --- a/src/dialogs/interactioninfodocument.cpp +++ b/src/dialogs/interactioninfodocument.cpp @@ -69,7 +69,9 @@ void InteractionInfoDocument::updateContent() { tabLayout->addWidget(textEdit); QTextCursor cursor(textEdit->document()); + cursor.beginEditBlock(); insertInteractionEnergiesForModel(interactions, cursor, model); + cursor.endEditBlock(); m_tabWidget->addTab(tab, model); } @@ -101,7 +103,7 @@ void InteractionInfoDocument::insertInteractionEnergiesForModel( } QList sortedComponents = getOrderedComponents(uniqueComponents); - QStringList tableHeader{"Color", "Distance", "Symmetry"}; + QStringList tableHeader{"Color", "N", "Distance", "Symmetry"}; tableHeader.append(sortedComponents); InfoTable infoTable(cursor, results->filterByModel(model).size() + 1, @@ -114,6 +116,8 @@ void InteractionInfoDocument::insertInteractionEnergiesForModel( int column = 0; infoTable.insertColorBlock(row, column++, result->color()); + infoTable.insertCellValue(row, column++, QString::number(result->count()), + Qt::AlignRight); infoTable.insertCellValue( row, column++, QString::number(result->centroidDistance(), 'f', 2), Qt::AlignRight); @@ -146,7 +150,7 @@ QList InteractionInfoDocument::getOrderedComponents( const QSet &uniqueComponents) { QList knownComponentsOrder; knownComponentsOrder << "coulomb" << "repulsion" << "exchange" - << "dispersion"; + << "polarization" << "dispersion"; QList sortedComponents; @@ -189,6 +193,7 @@ void InteractionInfoDocument::updateTableColors() { QTextDocument *doc = textEdit->document(); QTextCursor cursor(doc); + cursor.beginEditBlock(); QTextTable *table = cursor.currentTable(); if (!table) @@ -202,7 +207,13 @@ void InteractionInfoDocument::updateTableColors() { QTextCharFormat format = cell.format(); format.setBackground(result->color()); cell.setFormat(format); + QTextTableCell countCell = table->cellAt(row, 1); + QTextCursor countCursor = countCell.firstCursorPosition(); + countCursor.select(QTextCursor::BlockUnderCursor); + countCursor.removeSelectedText(); + countCursor.insertText(QString::number(result->count())); ++row; } + cursor.endEditBlock(); } } diff --git a/src/graphics/billboardrenderer.cpp b/src/graphics/billboardrenderer.cpp index 3be801b..0a8f9f9 100644 --- a/src/graphics/billboardrenderer.cpp +++ b/src/graphics/billboardrenderer.cpp @@ -151,6 +151,7 @@ void BillboardRenderer::clear() { m_indices.clear(); m_vertices.clear(); m_labels.clear(); + m_textures.clear(); m_numberOfIndices = 0; updateBuffers(); } diff --git a/src/graphics/chemicalstructurerenderer.cpp b/src/graphics/chemicalstructurerenderer.cpp index b160b5b..115218e 100644 --- a/src/graphics/chemicalstructurerenderer.cpp +++ b/src/graphics/chemicalstructurerenderer.cpp @@ -186,6 +186,7 @@ void ChemicalStructureRenderer::forceUpdates() { m_atomsNeedsUpdate = true; m_bondsNeedsUpdate = true; m_meshesNeedsUpdate = true; + m_frameworkRenderer->forceUpdates(); } void ChemicalStructureRenderer::updateLabels() { diff --git a/src/graphics/frameworkrenderer.cpp b/src/graphics/frameworkrenderer.cpp index 4b4c2d7..3db479f 100644 --- a/src/graphics/frameworkrenderer.cpp +++ b/src/graphics/frameworkrenderer.cpp @@ -10,15 +10,12 @@ FrameworkRenderer::FrameworkRenderer(ChemicalStructure *structure, m_ellipsoidRenderer = new EllipsoidRenderer(); m_cylinderRenderer = new CylinderRenderer(); m_lineRenderer = new LineRenderer(); + m_labelRenderer = new BillboardRenderer(); m_interactionComponentColors = { - {"coulomb", QColor("#a40000")}, - {"polarization", QColor("#b86092")}, - {"exchange", QColor("#721b3e")}, - {"repulsion", QColor("#ffcd12")}, - {"dispersion", QColor("#007e2f")}, - {"total", QColor("#16317d")} - }; + {"coulomb", QColor("#a40000")}, {"polarization", QColor("#b86092")}, + {"exchange", QColor("#721b3e")}, {"repulsion", QColor("#ffcd12")}, + {"dispersion", QColor("#007e2f")}, {"total", QColor("#16317d")}}; m_defaultInteractionComponentColor = QColor("#00b7a7"); update(structure); @@ -45,17 +42,20 @@ void FrameworkRenderer::updateRendererUniforms( } void FrameworkRenderer::updateInteractions() { m_needsUpdate = true; } +void FrameworkRenderer::forceUpdates() { m_needsUpdate = true; } void FrameworkRenderer::beginUpdates() { m_lineRenderer->beginUpdates(); m_ellipsoidRenderer->beginUpdates(); m_cylinderRenderer->beginUpdates(); + m_labelRenderer->beginUpdates(); } void FrameworkRenderer::endUpdates() { m_lineRenderer->endUpdates(); m_ellipsoidRenderer->endUpdates(); m_cylinderRenderer->endUpdates(); + m_labelRenderer->endUpdates(); } void FrameworkRenderer::setModel(const QString &model) { @@ -76,52 +76,61 @@ void FrameworkRenderer::handleInteractionsUpdate() { m_ellipsoidRenderer->clear(); m_lineRenderer->clear(); m_cylinderRenderer->clear(); + m_labelRenderer->clear(); if (m_options.display == FrameworkOptions::Display::None) return; FragmentPairSettings pairSettings; - pairSettings.allowInversion = m_options.allowInversion & m_interactions->hasInversionSymmetry(m_options.model); + pairSettings.allowInversion = + m_options.allowInversion & + m_interactions->hasInversionSymmetry(m_options.model); auto fragmentPairs = m_structure->findFragmentPairs(pairSettings); qDebug() << "Number of unique pairs:" << fragmentPairs.uniquePairs.size(); auto interactionMap = m_interactions->getInteractionsMatchingFragments( fragmentPairs.uniquePairs); - auto uniqueInteractions = interactionMap.value(m_options.model, {}); if (uniqueInteractions.size() < fragmentPairs.uniquePairs.size()) return; QColor color = m_options.customColor; - if(m_options.coloring == FrameworkOptions::Coloring::Component) { - color = m_interactionComponentColors.value(m_options.component, m_defaultInteractionComponentColor); + if (m_options.coloring == FrameworkOptions::Coloring::Component) { + color = m_interactionComponentColors.value( + m_options.component, m_defaultInteractionComponentColor); } - ColorMapFunc cmap(ColorMapName::Turbo, 0.0, 100.0); - std::vector> energies; energies.reserve(uniqueInteractions.size()); - for(const auto &pair: fragmentPairs.uniquePairs) { + for (const auto &pair : fragmentPairs.uniquePairs) { qDebug() << pair.index; } - for(const auto *interaction: uniqueInteractions) { + double emin = std::numeric_limits::max(); + double emax = std::numeric_limits::min(); + + for (const auto *interaction : uniqueInteractions) { QColor c = color; double energy = 0.0; if (interaction) { energy = interaction->getComponent(m_options.component); - qDebug() << "Interaction found: " << interaction->pairIndex(); - if(m_options.coloring == FrameworkOptions::Coloring::Interaction) { + if (m_options.coloring == FrameworkOptions::Coloring::Interaction) { c = interaction->color(); } } - if(m_options.coloring == FrameworkOptions::Coloring::Value) { - c = cmap(energy); - } + emin = qMin(energy, emin); + emax = qMax(energy, emax); energies.push_back({c, energy}); } + if (m_options.coloring == FrameworkOptions::Coloring::Value) { + ColorMapFunc cmap(ColorMapName::Turbo, emin, emax); + for (auto &[color, energy] : energies) { + color = cmap(energy); + } + } + const bool inv = pairSettings.allowInversion; for (const auto &[fragIndex, molPairs] : fragmentPairs.pairs) { @@ -137,29 +146,30 @@ void FrameworkRenderer::handleInteractionsUpdate() { QVector3D va(ca.x(), ca.y(), ca.z()); auto cb = pair.b.centroid(); QVector3D vb(cb.x(), cb.y(), cb.z()); + QVector3D m = va + (vb - va) * 0.5; - if(inv) { + if (inv) { if (m_options.display == FrameworkOptions::Display::Tubes) { cx::graphics::addSphereToEllipsoidRenderer(m_ellipsoidRenderer, va, color, scale); cx::graphics::addSphereToEllipsoidRenderer(m_ellipsoidRenderer, vb, color, scale); - cx::graphics::addCylinderToCylinderRenderer(m_cylinderRenderer, va, vb, - color, color, scale); + cx::graphics::addCylinderToCylinderRenderer(m_cylinderRenderer, va, + vb, color, color, scale); } else if (m_options.display == FrameworkOptions::Display::Lines) { cx::graphics::addLineToLineRenderer(*m_lineRenderer, va, vb, scale, color); + cx::graphics::addTextToBillboardRenderer(*m_labelRenderer, m, + QString::number(energy, 'd', 1)); } - } - else { - vb = va + (vb - va) * 0.5; + } else { if (m_options.display == FrameworkOptions::Display::Tubes) { cx::graphics::addSphereToEllipsoidRenderer(m_ellipsoidRenderer, va, color, scale); - cx::graphics::addCylinderToCylinderRenderer(m_cylinderRenderer, va, vb, + cx::graphics::addCylinderToCylinderRenderer(m_cylinderRenderer, va, m, color, color, scale); } else if (m_options.display == FrameworkOptions::Display::Lines) { - cx::graphics::addLineToLineRenderer(*m_lineRenderer, va, vb, scale, + cx::graphics::addLineToLineRenderer(*m_lineRenderer, va, m, scale, color); } } @@ -190,6 +200,11 @@ void FrameworkRenderer::draw(bool forPicking) { m_uniforms.apply(m_lineRenderer); m_lineRenderer->draw(); m_lineRenderer->release(); + + m_labelRenderer->bind(); + m_uniforms.apply(m_labelRenderer); + m_labelRenderer->draw(); + m_labelRenderer->release(); } } // namespace cx::graphics diff --git a/src/graphics/frameworkrenderer.h b/src/graphics/frameworkrenderer.h index b22096a..520f416 100644 --- a/src/graphics/frameworkrenderer.h +++ b/src/graphics/frameworkrenderer.h @@ -1,6 +1,7 @@ #pragma once #include "chemicalstructure.h" #include "colormap.h" +#include "billboardrenderer.h" #include "cylinderrenderer.h" #include "ellipsoidrenderer.h" #include "frameworkoptions.h" @@ -29,6 +30,8 @@ class FrameworkRenderer : public QObject { void beginUpdates(); void endUpdates(); + void forceUpdates(); + void setModel(const QString &); void setComponent(const QString &); @@ -56,6 +59,7 @@ class FrameworkRenderer : public QObject { LineRenderer *m_lineRenderer{nullptr}; EllipsoidRenderer *m_ellipsoidRenderer{nullptr}; CylinderRenderer *m_cylinderRenderer{nullptr}; + BillboardRenderer *m_labelRenderer{nullptr}; ChemicalStructure *m_structure{nullptr}; diff --git a/src/graphics/graphics.cpp b/src/graphics/graphics.cpp index f30f56c..ec3a425 100644 --- a/src/graphics/graphics.cpp +++ b/src/graphics/graphics.cpp @@ -222,9 +222,8 @@ void addTextToBillboardRenderer(BillboardRenderer &b, const QVector3D &position, int fontSize = settings::readSetting(settings::keys::TEXT_FONT_SIZE).toInt(); QFont font(fontName, fontSize); - font.setWeight(QFont::Weight::Bold); QFontMetrics fm(font); - int padding = 5; + int padding = fontSize / 4; int pixelsWide = fm.horizontalAdvance(text); int pixelsHigh = fm.height(); QImage img = QImage(QSize(pixelsWide + 2 * padding, pixelsHigh + padding), @@ -240,9 +239,15 @@ void addTextToBillboardRenderer(BillboardRenderer &b, const QVector3D &position, textOptions); painter.end(); - QImage sdf = distance_transform_2d(img); - QOpenGLTexture *texture = - new QOpenGLTexture(sdf.mirrored(), QOpenGLTexture::DontGenerateMipMaps); + QImage sdf = signed_distance_transform_2d(img); + QOpenGLTexture *texture = new QOpenGLTexture(QOpenGLTexture::Target2D); + texture->setSize(sdf.width(), sdf.height()); + texture->setFormat(QOpenGLTexture::R8_UNorm); + texture->allocateStorage(); + texture->setData(QOpenGLTexture::Red, QOpenGLTexture::UInt8, sdf.mirrored().constBits()); + texture->setMinificationFilter(QOpenGLTexture::Linear); + texture->setMagnificationFilter(QOpenGLTexture::Linear); + texture->setWrapMode(QOpenGLTexture::ClampToEdge); b.addVertices( {BillboardVertex( position, diff --git a/src/graphics/scene.cpp b/src/graphics/scene.cpp index 1171472..467b317 100644 --- a/src/graphics/scene.cpp +++ b/src/graphics/scene.cpp @@ -1039,6 +1039,7 @@ void Scene::textSettingsChanged() { settings::readSetting(settings::keys::TEXT_SMOOTHING).toFloat(); m_uniforms.u_textSDFOutline = settings::readSetting(settings::keys::TEXT_OUTLINE).toFloat(); + setNeedsUpdate(); } void Scene::lightSettingsChanged() { @@ -1463,15 +1464,18 @@ void Scene::colorFragmentsByEnergyPair(FragmentPairSettings pairSettings) { auto selectedFragments = m_structure->selectedFragments(); if (selectedFragments.size() == 1) { auto *interactions = m_structure->pairInteractions(); + interactions->resetCounts(); pairSettings.keyFragment = selectedFragments[0]; auto fragmentPairs = m_structure->findFragmentPairs(pairSettings); ColorMapFunc colorMap(ColorMapName::Austria, 0, fragmentPairs.uniquePairs.size() - 1); + std::vector counts(fragmentPairs.uniquePairs.size(), 0); for (const auto &[fragmentPair, idx] : fragmentPairs.pairs[selectedFragments[0]]) { qDebug() << "Setting fragment color" << fragmentPair.index.b << colorMap(idx); QColor c = colorMap(idx); + counts[idx]++; m_structure->setFragmentColor(fragmentPair.index.b, c); } auto interactionMap = interactions->getInteractionsMatchingFragments( @@ -1480,6 +1484,7 @@ void Scene::colorFragmentsByEnergyPair(FragmentPairSettings pairSettings) { for (int i = 0; i < interactionList.size(); i++) { if (interactionList[i]) { interactionList[i]->setColor(colorMap(i)); + interactionList[i]->setCount(counts[i]); } } } @@ -1495,7 +1500,8 @@ void Scene::clearFragmentColors() { } void Scene::togglePairHighlighting(bool show) { - if(!m_structure) return; + if (!m_structure) + return; if (show) { _highlightMode = HighlightMode::Pair; diff --git a/src/graphics/signed_distance_field.h b/src/graphics/signed_distance_field.h index d75b1a1..bac5985 100644 --- a/src/graphics/signed_distance_field.h +++ b/src/graphics/signed_distance_field.h @@ -1,185 +1,122 @@ #pragma once +#include #include #include #include -#include - -#define DISTANCE_INF_SENTINEL 1e20f - -inline void distance_transform_1d(const std::vector &f, - std::vector &d, std::vector &v, - std::vector &z, int n) { - int k = 0; - v[0] = 0; - z[0] = -DISTANCE_INF_SENTINEL; - z[1] = DISTANCE_INF_SENTINEL; - auto square = [](auto x) { return x * x; }; - - for (int index = 1; index <= n - 1; index++) { - float s = ((f[index] + square(index)) - (f[v[k]] + square(v[k]))) / - (2 * index - 2 * v[k]); - while (s <= z[k]) { - k--; - s = ((f[index] + square(index)) - (f[v[k]] + square(v[k]))) / - (2 * index - 2 * v[k]); - } - k++; - v[k] = index; - z[k] = s; - z[k + 1] = DISTANCE_INF_SENTINEL; - } - - k = 0; - for (int q = 0; q <= n - 1; q++) { - while (z[k + 1] < q) - k++; - d[q] = square(q - v[k]) + f[v[k]]; - } + +namespace impl { +constexpr float DISTANCE_INF_SENTINEL{1e20f}; } -inline std::vector -grayscale_to_float(const QImage &im, - uchar on = std::numeric_limits::max()) { - int width = im.width(); - int height = im.height(); - - std::vector out(width * height); - for (int y = 0; y < height; y++) { - const uchar *bits = im.scanLine(y); - for (int x = 0; x < width; x++) { - if (bits[x] >= on) - out[y * width + x] = 0; - else - out[y * width + x] = DISTANCE_INF_SENTINEL; +// Helper function to convert QImage to Eigen::MatrixXf +Eigen::MatrixXf qImageToEigenMatrix(const QImage& image, bool invert = false) { + int width = image.width(); + int height = image.height(); + Eigen::MatrixXf matrix(height, width); + + for (int y = 0; y < height; ++y) { + const uchar* scanLine = image.constScanLine(y); + for (int x = 0; x < width; ++x) { + bool isSet = scanLine[x] < 128; // Threshold at 128 + if (invert) isSet = !isSet; + matrix(y, x) = isSet ? 0 : impl::DISTANCE_INF_SENTINEL; + } } - } - return out; -} -template -inline T bound(const T &x, const T &lower, const T &upper) { - return (x < lower ? lower : (x > upper ? upper : x)); + return matrix; } -QImage to_8bit_grayscale(const std::vector &data, int width, int height, - float l, float u) { - QImage result = QImage(QSize(width, height), QImage::Format_Grayscale8); - float scale = std::numeric_limits::max() / (u - l); - for (int y = 0; y < height; y++) { - uchar *bits = result.scanLine(y); - for (int x = 0; x < width; x++) { - uchar val = static_cast((data[y * width + x] - l) * scale); - bits[x] = bound(val, uchar{0}, std::numeric_limits::max()); +// Helper function to convert Eigen::MatrixXf to QImage +QImage eigenMatrixToQImage(const Eigen::MatrixXf& matrix) { + int height = matrix.rows(); + int width = matrix.cols(); + QImage result(width, height, QImage::Format_Grayscale8); + + float minVal = matrix.minCoeff(); + float maxVal = matrix.maxCoeff(); + float range = std::max(std::abs(minVal), std::abs(maxVal)) * 2; + + for (int y = 0; y < height; ++y) { + uchar* scanLine = result.scanLine(y); + for (int x = 0; x < width; ++x) { + float normalizedVal = (matrix(y, x) / range) + 0.5f; + normalizedVal = std::max(0.0f, std::min(1.0f, normalizedVal)); + scanLine[x] = static_cast(std::round(normalizedVal * 255)); + } } - } - return result; + + return result; } -/* dt of 2d function using squared distance */ -inline QImage -distance_transform_2d(const QImage &image, - uchar on = std::numeric_limits::max()) { - int width = image.width(); - int height = image.height(); - const int n = std::max(width, height); - std::vector f(n); - std::vector d(n); - std::vector v(n); - std::vector z(n + 1); - std::vector dtdata = grayscale_to_float(image, on); - - // transform along columns - for (int x = 0; x < width; x++) { - for (int y = 0; y < height; y++) { - f[y] = dtdata[y * width + x]; - } - distance_transform_1d(f, d, v, z, height); - for (int y = 0; y < height; y++) { - dtdata[y * width + x] = d[y]; - } - } +// 1D distance transform (helper function) +void distance_transform_1d(Eigen::VectorXf& f, Eigen::VectorXf& d, Eigen::VectorXi& v, Eigen::VectorXf& z) { + int n = f.size(); + int k = 0; + v(0) = 0; + z(0) = -impl::DISTANCE_INF_SENTINEL; + z(1) = impl::DISTANCE_INF_SENTINEL; - // transform along rows - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - f[x] = dtdata[y * width + x]; + auto square = [](auto x) { return x * x; }; + + for (int index = 1; index < n; ++index) { + float s = ((f(index) + square(index)) - (f(v(k)) + square(v(k)))) / (2 * index - 2 * v(k)); + while (s <= z(k)) { + k--; + s = ((f(index) + square(index)) - (f(v(k)) + square(v(k)))) / (2 * index - 2 * v(k)); + } + k++; + v(k) = index; + z(k) = s; + z(k + 1) = impl::DISTANCE_INF_SENTINEL; } - distance_transform_1d(f, d, v, z, width); - for (int x = 0; x < width; x++) { - dtdata[y * width + x] = d[x]; + + k = 0; + for (int q = 0; q < n; ++q) { + while (z(k + 1) < q) + k++; + d(q) = square(q - v(k)) + f(v(k)); } - } - std::transform(dtdata.begin(), dtdata.end(), dtdata.begin(), - [](double x) { return std::sqrt(x); }); - auto minmax = std::minmax_element(dtdata.begin(), dtdata.end()); - float l = *minmax.first; - float u = *minmax.second; - return to_8bit_grayscale(dtdata, width, height, l, u); } -/* dt of 2d function using squared distance */ -inline QImage signed_distance_transform_2d(const QImage &image) { - int width = image.width(); - int height = image.height(); - const int n = std::max(width, height); - std::vector f(n); - std::vector d(n); - std::vector v(n); - std::vector z(n + 1); - std::vector dtdata1 = grayscale_to_float(image); - std::vector dtdata2 = grayscale_to_float(image, 0); - - // transform along columns - for (int x = 0; x < width; x++) { - for (int y = 0; y < height; y++) { - f[y] = dtdata1[y * width + x]; - } - distance_transform_1d(f, d, v, z, height); - for (int y = 0; y < height; y++) { - dtdata1[y * width + x] = d[y]; - } - } +// 2D Signed Distance Transform +QImage signed_distance_transform_2d(const QImage& image) { + int width = image.width(); + int height = image.height(); + int n = std::max(width, height); - // transform along rows - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - f[x] = dtdata1[y * width + x]; - } - distance_transform_1d(f, d, v, z, width); - for (int x = 0; x < width; x++) { - dtdata1[y * width + x] = d[x]; - } - } - // transform along columns - for (int x = 0; x < width; x++) { - for (int y = 0; y < height; y++) { - f[y] = dtdata2[y * width + x]; - } - distance_transform_1d(f, d, v, z, height); - for (int y = 0; y < height; y++) { - dtdata2[y * width + x] = d[y]; - } - } + Eigen::MatrixXf dtdata1 = qImageToEigenMatrix(image, false); + Eigen::MatrixXf dtdata2 = qImageToEigenMatrix(image, true); + + Eigen::VectorXf f(n), d(n); + Eigen::VectorXi v(n); + Eigen::VectorXf z(n + 1); - // transform along rows - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - f[x] = dtdata2[y * width + x]; + // Transform along columns + for (int x = 0; x < width; ++x) { + f = dtdata1.col(x); + distance_transform_1d(f, d, v, z); + dtdata1.col(x) = d.head(height); + + f = dtdata2.col(x); + distance_transform_1d(f, d, v, z); + dtdata2.col(x) = d.head(height); } - distance_transform_1d(f, d, v, z, width); - for (int x = 0; x < width; x++) { - dtdata2[y * width + x] = d[x]; + + // Transform along rows + for (int y = 0; y < height; ++y) { + f = dtdata1.row(y).transpose(); + distance_transform_1d(f, d, v, z); + dtdata1.row(y) = d.head(width).transpose(); + + f = dtdata2.row(y).transpose(); + distance_transform_1d(f, d, v, z); + dtdata2.row(y) = d.head(width).transpose(); } - } - std::transform(dtdata1.begin(), dtdata1.end(), dtdata1.begin(), - [](double x) { return std::sqrt(x); }); - std::transform(dtdata2.begin(), dtdata2.end(), dtdata2.begin(), - [](double x) { return std::sqrt(x); }); - for (int i = 0; i < dtdata1.size(); i++) { - dtdata1[i] = dtdata1[i] - dtdata2[i]; - } - auto minmax = std::minmax_element(dtdata1.begin(), dtdata1.end()); - float l = *minmax.first; - float u = *minmax.second; - return to_8bit_grayscale(dtdata1, width, height, l, u); + + dtdata1 = dtdata1.array().sqrt(); + dtdata2 = dtdata2.array().sqrt(); + + Eigen::MatrixXf result = dtdata1 - dtdata2; + + return eigenMatrixToQImage(result); } diff --git a/src/preferencesdialog.cpp b/src/preferencesdialog.cpp index 5dfadcb..1b914be 100644 --- a/src/preferencesdialog.cpp +++ b/src/preferencesdialog.cpp @@ -227,10 +227,12 @@ void PreferencesDialog::initConnections() { connect(textOutlineWidthSlider, &QAbstractSlider::valueChanged, this, &PreferencesDialog::setTextSliders); - connect(textBufferWidthSlider, &QAbstractSlider::valueChanged, this, - &PreferencesDialog::setTextSliders); + connect(textFontSizeSlider, &QAbstractSlider::valueChanged, this, + &PreferencesDialog::onTextFontSizeChanged); connect(textSmoothingWidthSlider, &QAbstractSlider::valueChanged, this, &PreferencesDialog::setTextSliders); + connect(textBufferWidthSlider, &QAbstractSlider::valueChanged, this, + &PreferencesDialog::setTextSliders); connect(textFontComboBox, &QFontComboBox::currentFontChanged, this, &PreferencesDialog::onTextFontFamilyChanged); @@ -535,11 +537,13 @@ void PreferencesDialog::updateDialogFromSettings() { textOutlineWidthSlider->setValue(static_cast( settings::readSetting(settings::keys::TEXT_OUTLINE).toFloat() * 100.0f)); - textBufferWidthSlider->setValue(static_cast( - settings::readSetting(settings::keys::TEXT_BUFFER).toFloat() * 100.0f)); + textFontSizeSlider->setValue(static_cast( + settings::readSetting(settings::keys::TEXT_FONT_SIZE).toInt())); textSmoothingWidthSlider->setValue(static_cast( settings::readSetting(settings::keys::TEXT_SMOOTHING).toFloat() * 100.0f)); + textBufferWidthSlider->setValue(static_cast( + settings::readSetting(settings::keys::TEXT_BUFFER).toFloat() * 100.0f)); glDepthTestEnabledCheckBox->setChecked( settings::readSetting(settings::keys::ENABLE_DEPTH_TEST).toBool()); @@ -761,7 +765,6 @@ void PreferencesDialog::setTextSliders(int value) { const auto val = m_textSliderKeys.constFind(senderName); if (val == m_textSliderKeys.constEnd()) return; - settings::writeSetting(val.value(), value / 100.0f); emit textSettingsChanged(); } @@ -868,5 +871,10 @@ void PreferencesDialog::populateExecutablesFromPath(bool override) { void PreferencesDialog::onTextFontFamilyChanged(const QFont &font) { settings::writeSetting(settings::keys::TEXT_FONT_FAMILY, font.family()); - settings::writeSetting(settings::keys::TEXT_FONT_SIZE, font.pointSize()); + emit textSettingsChanged(); +} + +void PreferencesDialog::onTextFontSizeChanged(int size) { + settings::writeSetting(settings::keys::TEXT_FONT_SIZE, size); + emit textSettingsChanged(); } diff --git a/src/preferencesdialog.h b/src/preferencesdialog.h index 61ee0d7..2356511 100644 --- a/src/preferencesdialog.h +++ b/src/preferencesdialog.h @@ -69,6 +69,7 @@ private slots: void setTextSliders(int); void restoreDefaultLightingSettings(); void onTextFontFamilyChanged(const QFont &font); + void onTextFontSizeChanged(int); private: void updateLightPositions(); diff --git a/src/preferencesdialog.ui b/src/preferencesdialog.ui index 4c60e7b..f6958f6 100644 --- a/src/preferencesdialog.ui +++ b/src/preferencesdialog.ui @@ -166,52 +166,45 @@ - - - - - - - - - + + - - + + - Frameworks Repulsive Energy Color + Selection Color - - + + - Surface Face Highlight Color + Background Color - - + + - Background Color + Text Label Color - - + + - - + + - + Text Outline Color @@ -222,24 +215,24 @@ - - + + - - + + - Text Label Color + - - + + - Text Outline Color + Frameworks Repulsive Energy Color @@ -250,17 +243,24 @@ - - + + - - + + - Selection Color + + + + + + + + Surface Face Highlight Color @@ -511,18 +511,28 @@ - Buffer + Buffer Width - + - Outline Width + Text Size - + + + + + Arial + 128 + + + + + Qt::Horizontal @@ -533,25 +543,51 @@ - + + + 1 + + + 256 + Qt::Horizontal - - false - QSlider::TicksBelow + + 12 + - + Smoothing + + + + Outline Width + + + + + + + Qt::Horizontal + + + false + + + QSlider::TicksBelow + + + @@ -569,16 +605,6 @@ - - - - - Arial - 128 - - - -