jfjoch_viewer: Work in progress on reciprocal space viewer

This commit is contained in:
2026-06-01 11:00:46 +02:00
parent baeef1960e
commit a2fd0a433f
2 changed files with 130 additions and 25 deletions
@@ -8,6 +8,7 @@
#include <QPushButton>
#include <QSurfaceFormat>
#include <QtMath>
#include <algorithm>
// ============================================================================
// Shaders
@@ -33,10 +34,13 @@ static const char *kFragSrc = R"(
#version 330 core
in vec3 vColor;
out vec4 fragColor;
uniform bool uPointShape;
void main() {
// Discard corners to draw a circle instead of a square point.
vec2 c = gl_PointCoord - vec2(0.5);
if (dot(c, c) > 0.25) discard;
if (uPointShape) {
// Discard corners to draw a circle instead of a square point.
vec2 c = gl_PointCoord - vec2(0.5);
if (dot(c, c) > 0.25) discard;
}
fragColor = vec4(vColor, 1.0);
}
)";
@@ -82,6 +86,14 @@ void ReciprocalSpaceGLView::initializeGL() {
// Create empty VAO/VBOs so they are valid before the first data arrives.
setupVAO(spotsVAO_, spotsVBO_);
setupVAO(linesVAO_, linesVBO_);
glReady_ = true;
uploadBuffer(spotsVBO_, spotsVAO_, pendingSpots_);
spotsCount_ = static_cast<int>(pendingSpots_.size());
uploadBuffer(linesVBO_, linesVAO_, pendingLines_);
linesCount_ = static_cast<int>(pendingLines_.size());
}
void ReciprocalSpaceGLView::resizeGL(int w, int h) {
@@ -89,22 +101,30 @@ void ReciprocalSpaceGLView::resizeGL(int w, int h) {
proj_.perspective(45.0f, float(w) / float(h ? h : 1), 0.1f, 10000.0f);
}
void ReciprocalSpaceGLView::paintGL() {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Build view matrix from orbit camera state.
QMatrix4x4 ReciprocalSpaceGLView::currentViewMatrix() const {
QMatrix4x4 view;
view.translate(0, 0, -zoom_);
view.rotate(pitch_, 1, 0, 0);
view.rotate(yaw_, 0, 1, 0);
view.translate(-target_);
return view;
}
const QMatrix4x4 mvp = proj_ * view;
QMatrix4x4 ReciprocalSpaceGLView::currentMvpMatrix() const {
return proj_ * currentViewMatrix();
}
void ReciprocalSpaceGLView::paintGL() {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
const QMatrix4x4 mvp = currentMvpMatrix();
shader_.bind();
shader_.setUniformValue("uMVP", mvp);
// Draw axes + cell vectors
if (linesCount_ > 0) {
shader_.setUniformValue("uPointShape", false);
linesVAO_.bind();
glDrawArrays(GL_LINES, 0, linesCount_);
linesVAO_.release();
@@ -112,6 +132,7 @@ void ReciprocalSpaceGLView::paintGL() {
// Draw spots
if (spotsCount_ > 0) {
shader_.setUniformValue("uPointShape", true);
spotsVAO_.bind();
glDrawArrays(GL_POINTS, 0, spotsCount_);
spotsVAO_.release();
@@ -123,18 +144,28 @@ void ReciprocalSpaceGLView::paintGL() {
// ---------------------------------------------------------------------------
void ReciprocalSpaceGLView::setSpots(const std::vector<Vertex> &spots) {
makeCurrent();
uploadBuffer(spotsVBO_, spotsVAO_, spots);
spotsCount_ = static_cast<int>(spots.size());
doneCurrent();
pendingSpots_ = spots;
spotsCount_ = static_cast<int>(pendingSpots_.size());
if (glReady_) {
makeCurrent();
uploadBuffer(spotsVBO_, spotsVAO_, pendingSpots_);
doneCurrent();
}
update();
}
void ReciprocalSpaceGLView::setLines(const std::vector<Vertex> &lines) {
makeCurrent();
uploadBuffer(linesVBO_, linesVAO_, lines);
linesCount_ = static_cast<int>(lines.size());
doneCurrent();
pendingLines_ = lines;
linesCount_ = static_cast<int>(pendingLines_.size());
if (glReady_) {
makeCurrent();
uploadBuffer(linesVBO_, linesVAO_, pendingLines_);
doneCurrent();
}
update();
}
@@ -185,6 +216,15 @@ void ReciprocalSpaceGLView::mousePressEvent(QMouseEvent *e) {
lastMousePos_ = e->pos();
}
void ReciprocalSpaceGLView::mouseDoubleClickEvent(QMouseEvent *e) {
if (e->button() == Qt::LeftButton && focusNearestSpot(e->pos())) {
e->accept();
return;
}
QOpenGLWidget::mouseDoubleClickEvent(e);
}
void ReciprocalSpaceGLView::mouseMoveEvent(QMouseEvent *e) {
const QPoint delta = e->pos() - lastMousePos_;
lastMousePos_ = e->pos();
@@ -194,6 +234,19 @@ void ReciprocalSpaceGLView::mouseMoveEvent(QMouseEvent *e) {
pitch_ += delta.y() * 0.5f;
pitch_ = qBound(-89.0f, pitch_, 89.0f);
update();
} else if (e->buttons() & Qt::MiddleButton) {
const float panScale = zoom_ * 0.0015f;
QMatrix4x4 rot;
rot.rotate(pitch_, 1, 0, 0);
rot.rotate(yaw_, 0, 1, 0);
const QVector3D right = rot.inverted().mapVector(QVector3D(1, 0, 0));
const QVector3D up = rot.inverted().mapVector(QVector3D(0, 1, 0));
target_ -= right * float(delta.x()) * panScale;
target_ += up * float(delta.y()) * panScale;
update();
}
}
@@ -203,6 +256,49 @@ void ReciprocalSpaceGLView::wheelEvent(QWheelEvent *e) {
update();
}
bool ReciprocalSpaceGLView::focusNearestSpot(const QPoint &screenPos) {
if (pendingSpots_.empty() || width() <= 0 || height() <= 0)
return false;
const QMatrix4x4 mvp = currentMvpMatrix();
float bestDist2 = std::numeric_limits<float>::max();
QVector3D bestPos;
bool found = false;
for (const auto &v : pendingSpots_) {
const QVector4D clip = mvp * QVector4D(v.x, v.y, v.z, 1.0f);
if (clip.w() <= 0.0f)
continue;
const QVector3D ndc = clip.toVector3DAffine();
if (ndc.z() < -1.0f || ndc.z() > 1.0f)
continue;
const float sx = (ndc.x() * 0.5f + 0.5f) * float(width());
const float sy = (0.5f - ndc.y() * 0.5f) * float(height());
const float dx = sx - float(screenPos.x());
const float dy = sy - float(screenPos.y());
const float dist2 = dx * dx + dy * dy;
if (dist2 < bestDist2) {
bestDist2 = dist2;
bestPos = QVector3D(v.x, v.y, v.z);
found = true;
}
}
// Roughly 15 px picking radius.
if (!found || bestDist2 > 15.0f * 15.0f)
return false;
target_ = bestPos;
zoom_ = qMax(5.0f, zoom_ * 0.35f);
update();
return true;
}
// ============================================================================
// JFJochViewerReciprocalSpaceWindow
// ============================================================================
@@ -271,9 +367,7 @@ void JFJochViewerReciprocalSpaceWindow::datasetLoaded(
rebuildGL();
}
void JFJochViewerReciprocalSpaceWindow::imageLoaded(
std::shared_ptr<const JFJochReaderImage> image)
{
void JFJochViewerReciprocalSpaceWindow::imageLoaded(std::shared_ptr<const JFJochReaderImage> image) {
if (!accumulateCheck->isChecked()) {
spots_.clear();
indexed_lattice_.reset();
@@ -347,7 +441,7 @@ void JFJochViewerReciprocalSpaceWindow::rebuildGL() {
// --- Spots ---------------------------------------------------------------
std::vector<ReciprocalSpaceGLView::Vertex> spotVerts;
spotVerts.reserve(spots_.size());
spotVerts.reserve(spots_.size() + 1);
// Precompute the three possible colors as floats — avoids QColor in the loop.
auto toF = [](const QColor &c, float &r, float &g, float &b) {
@@ -370,6 +464,8 @@ void JFJochViewerReciprocalSpaceWindow::rebuildGL() {
r, g, b });
}
spotVerts.push_back({ 0, 0, 0, 1.0, 1.0, 1.0 });
// --- Lines: axes + optional cell vectors ---------------------------------
// Each segment = 2 vertices (GL_LINES draws pairs).
std::vector<ReciprocalSpaceGLView::Vertex> lineVerts;
@@ -383,10 +479,10 @@ void JFJochViewerReciprocalSpaceWindow::rebuildGL() {
};
// Reference axes
const float len = 10.0f;
addLine({0,0,0}, {len,0,0}, QColor(180, 60, 60));
addLine({0,0,0}, {0,len,0}, QColor(60, 180, 60));
addLine({0,0,0}, {0,0,len}, QColor(60, 60, 180));
const float len = 20.0f;
addLine({0,0,0}, {len,0,0}, QColor(255, 60, 60));
addLine({0,0,0}, {0,len,0}, QColor(60, 255, 60));
addLine({0,0,0}, {0,0,len}, QColor(60, 60, 255));
// Reciprocal cell vectors
if (showCellCheck->isChecked() && indexed_lattice_) {
@@ -46,6 +46,7 @@ protected:
void paintGL() override;
void mousePressEvent(QMouseEvent *e) override;
void mouseDoubleClickEvent(QMouseEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
void wheelEvent(QWheelEvent *e) override;
@@ -54,21 +55,29 @@ private:
const std::vector<Vertex> &data);
void setupVAO(QOpenGLVertexArrayObject &vao, QOpenGLBuffer &vbo);
QMatrix4x4 currentViewMatrix() const;
QMatrix4x4 currentMvpMatrix() const;
bool focusNearestSpot(const QPoint &screenPos);
QOpenGLShaderProgram shader_;
bool glReady_ = false;
QOpenGLVertexArrayObject spotsVAO_;
QOpenGLBuffer spotsVBO_;
int spotsCount_ = 0;
std::vector<Vertex> pendingSpots_;
QOpenGLVertexArrayObject linesVAO_;
QOpenGLBuffer linesVBO_;
int linesCount_ = 0;
std::vector<Vertex> pendingLines_;
// Camera state
QMatrix4x4 proj_;
float yaw_ = 0.0f; // degrees
float pitch_ = 20.0f; // degrees
float zoom_ = 80.0f; // distance from origin
float zoom_ = 80.0f; // distance from camera target
QVector3D target_{0.0f, 0.0f, 0.0f};
QPoint lastMousePos_;
};