diff --git a/viewer/windows/JFJochViewerReciprocalSpaceWindow.cpp b/viewer/windows/JFJochViewerReciprocalSpaceWindow.cpp index c2403cf0..20d420f7 100644 --- a/viewer/windows/JFJochViewerReciprocalSpaceWindow.cpp +++ b/viewer/windows/JFJochViewerReciprocalSpaceWindow.cpp @@ -8,6 +8,7 @@ #include #include #include +#include // ============================================================================ // 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(pendingSpots_.size()); + + uploadBuffer(linesVBO_, linesVAO_, pendingLines_); + linesCount_ = static_cast(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 &spots) { - makeCurrent(); - uploadBuffer(spotsVBO_, spotsVAO_, spots); - spotsCount_ = static_cast(spots.size()); - doneCurrent(); + pendingSpots_ = spots; + spotsCount_ = static_cast(pendingSpots_.size()); + + if (glReady_) { + makeCurrent(); + uploadBuffer(spotsVBO_, spotsVAO_, pendingSpots_); + doneCurrent(); + } + update(); } void ReciprocalSpaceGLView::setLines(const std::vector &lines) { - makeCurrent(); - uploadBuffer(linesVBO_, linesVAO_, lines); - linesCount_ = static_cast(lines.size()); - doneCurrent(); + pendingLines_ = lines; + linesCount_ = static_cast(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::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 image) -{ +void JFJochViewerReciprocalSpaceWindow::imageLoaded(std::shared_ptr image) { if (!accumulateCheck->isChecked()) { spots_.clear(); indexed_lattice_.reset(); @@ -347,7 +441,7 @@ void JFJochViewerReciprocalSpaceWindow::rebuildGL() { // --- Spots --------------------------------------------------------------- std::vector 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 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_) { diff --git a/viewer/windows/JFJochViewerReciprocalSpaceWindow.h b/viewer/windows/JFJochViewerReciprocalSpaceWindow.h index 06e9da28..d73d7a3a 100644 --- a/viewer/windows/JFJochViewerReciprocalSpaceWindow.h +++ b/viewer/windows/JFJochViewerReciprocalSpaceWindow.h @@ -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 &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 pendingSpots_; QOpenGLVertexArrayObject linesVAO_; QOpenGLBuffer linesVBO_; int linesCount_ = 0; + std::vector 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_; };