From baeef1960e88cf7e8ba904cae0eb0cbe056ab28d Mon Sep 17 00:00:00 2001 From: leonarski_f Date: Sun, 31 May 2026 19:59:13 +0200 Subject: [PATCH] JFJochViewerReciprocalSpaceWindow: OpenGL version, seems to work well --- viewer/CMakeLists.txt | 4 +- .../JFJochViewerReciprocalSpaceWindow.cpp | 413 ++++++++++++------ .../JFJochViewerReciprocalSpaceWindow.h | 141 +++--- 3 files changed, 364 insertions(+), 194 deletions(-) diff --git a/viewer/CMakeLists.txt b/viewer/CMakeLists.txt index c8f90ef8..0db42e76 100644 --- a/viewer/CMakeLists.txt +++ b/viewer/CMakeLists.txt @@ -5,7 +5,7 @@ SET(CMAKE_AUTOMOC ON) SET(CMAKE_AUTORCC ON) SET(CMAKE_AUTOUIC ON) -FIND_PACKAGE(Qt6 COMPONENTS Core Gui Widgets Charts DBus Concurrent 3DCore 3DRender 3DExtras 3DInput REQUIRED) +FIND_PACKAGE(Qt6 COMPONENTS Core Gui Widgets Charts DBus Concurrent OpenGL OpenGLWidgets REQUIRED) QT_ADD_RESOURCES(APP_RESOURCES resources/resources.qrc) @@ -89,7 +89,7 @@ ADD_EXECUTABLE(jfjoch_viewer jfjoch_viewer.cpp JFJochViewerWindow.cpp JFJochView ) TARGET_LINK_LIBRARIES(jfjoch_viewer Qt6::Core Qt6::Gui Qt6::Widgets Qt6::Charts Qt6::DBus Qt6::Concurrent - Qt6::3DCore Qt6::3DRender Qt6::3DExtras Qt6::3DInput + Qt6::OpenGL Qt6::OpenGLWidgets JFJochReader JFJochLogger JFJochCommon JFJochWriter JFJochImageAnalysis) INSTALL(TARGETS jfjoch_viewer RUNTIME COMPONENT viewer) diff --git a/viewer/windows/JFJochViewerReciprocalSpaceWindow.cpp b/viewer/windows/JFJochViewerReciprocalSpaceWindow.cpp index 31adc600..c2403cf0 100644 --- a/viewer/windows/JFJochViewerReciprocalSpaceWindow.cpp +++ b/viewer/windows/JFJochViewerReciprocalSpaceWindow.cpp @@ -3,91 +3,222 @@ #include "JFJochViewerReciprocalSpaceWindow.h" -#include #include #include #include +#include +#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +// ============================================================================ +// Shaders +// ============================================================================ -#include "../../common/CrystalLattice.h" +// One shader handles both spots (GL_POINTS) and lines (GL_LINES). +// Per-vertex color comes straight from the VBO; MVP is a single uniform. -namespace { +static const char *kVertSrc = R"( + #version 330 core + layout(location = 0) in vec3 aPos; + layout(location = 1) in vec3 aColor; + out vec3 vColor; + uniform mat4 uMVP; + void main() { + vColor = aColor; + gl_Position = uMVP * vec4(aPos, 1.0); + gl_PointSize = 6.0; + } +)"; -// Add a small sphere at the given position with the given color. -void AddBall(Qt3DCore::QEntity *parent, const QVector3D &pos, const QColor &color) { - auto *entity = new Qt3DCore::QEntity(parent); - auto *mesh = new Qt3DExtras::QSphereMesh(entity); - auto *material = new Qt3DExtras::QPhongMaterial(entity); - auto *transform = new Qt3DCore::QTransform(entity); +static const char *kFragSrc = R"( + #version 330 core + in vec3 vColor; + out vec4 fragColor; + 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; + fragColor = vec4(vColor, 1.0); + } +)"; - mesh->setRadius(0.4f); - mesh->setRings(6); // low-poly: plenty for a tiny spot - mesh->setSlices(6); - material->setDiffuse(color); - material->setAmbient(color.darker(150)); - transform->setTranslation(pos); +// ============================================================================ +// ReciprocalSpaceGLView +// ============================================================================ - entity->addComponent(mesh); - entity->addComponent(material); - entity->addComponent(transform); +ReciprocalSpaceGLView::ReciprocalSpaceGLView(QWidget *parent) + : QOpenGLWidget(parent) + , spotsVBO_(QOpenGLBuffer::VertexBuffer) + , linesVBO_(QOpenGLBuffer::VertexBuffer) +{ + // Request OpenGL 3.3 Core Profile (Linux/NVIDIA, Mesa, macOS all support this) + QSurfaceFormat fmt; + fmt.setVersion(3, 3); + fmt.setProfile(QSurfaceFormat::CoreProfile); + fmt.setDepthBufferSize(24); + setFormat(fmt); + setFocusPolicy(Qt::StrongFocus); } -// Draw a cylinder from the origin along the given (already scaled) vector. -void AddVector(Qt3DCore::QEntity *parent, const QVector3D &vec, - const QColor &color, float thickness) { - const float length = vec.length(); - if (length < 1e-6f) - return; - - auto *entity = new Qt3DCore::QEntity(parent); - auto *mesh = new Qt3DExtras::QCylinderMesh(entity); - auto *material = new Qt3DExtras::QPhongMaterial(entity); - auto *transform = new Qt3DCore::QTransform(entity); - - mesh->setRadius(thickness); - mesh->setLength(length); - material->setDiffuse(color); - - // QCylinderMesh points along +Y; rotate to match vec direction. - const QQuaternion rot = QQuaternion::rotationTo(QVector3D(0, 1, 0), vec.normalized()); - transform->setRotation(rot); - transform->setTranslation(vec * 0.5f); - - entity->addComponent(mesh); - entity->addComponent(material); - entity->addComponent(transform); +ReciprocalSpaceGLView::~ReciprocalSpaceGLView() { + makeCurrent(); + spotsVAO_.destroy(); + spotsVBO_.destroy(); + linesVAO_.destroy(); + linesVBO_.destroy(); + doneCurrent(); } -} // namespace +void ReciprocalSpaceGLView::initializeGL() { + initializeOpenGLFunctions(); + + glClearColor(20.f/255, 20.f/255, 25.f/255, 1.0f); + glEnable(GL_DEPTH_TEST); + glEnable(GL_PROGRAM_POINT_SIZE); // lets the vertex shader set gl_PointSize + + shader_.addShaderFromSourceCode(QOpenGLShader::Vertex, kVertSrc); + shader_.addShaderFromSourceCode(QOpenGLShader::Fragment, kFragSrc); + shader_.link(); + + // Create empty VAO/VBOs so they are valid before the first data arrives. + setupVAO(spotsVAO_, spotsVBO_); + setupVAO(linesVAO_, linesVBO_); +} + +void ReciprocalSpaceGLView::resizeGL(int w, int h) { + proj_.setToIdentity(); + 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 view; + view.translate(0, 0, -zoom_); + view.rotate(pitch_, 1, 0, 0); + view.rotate(yaw_, 0, 1, 0); + + const QMatrix4x4 mvp = proj_ * view; + + shader_.bind(); + shader_.setUniformValue("uMVP", mvp); + + // Draw axes + cell vectors + if (linesCount_ > 0) { + linesVAO_.bind(); + glDrawArrays(GL_LINES, 0, linesCount_); + linesVAO_.release(); + } + + // Draw spots + if (spotsCount_ > 0) { + spotsVAO_.bind(); + glDrawArrays(GL_POINTS, 0, spotsCount_); + spotsVAO_.release(); + } + + shader_.release(); +} // --------------------------------------------------------------------------- +void ReciprocalSpaceGLView::setSpots(const std::vector &spots) { + makeCurrent(); + uploadBuffer(spotsVBO_, spotsVAO_, spots); + spotsCount_ = static_cast(spots.size()); + doneCurrent(); + update(); +} + +void ReciprocalSpaceGLView::setLines(const std::vector &lines) { + makeCurrent(); + uploadBuffer(linesVBO_, linesVAO_, lines); + linesCount_ = static_cast(lines.size()); + doneCurrent(); + update(); +} + +// --------------------------------------------------------------------------- + +void ReciprocalSpaceGLView::setupVAO(QOpenGLVertexArrayObject &vao, + QOpenGLBuffer &vbo) { + vao.create(); + vao.bind(); + vbo.create(); + vbo.bind(); + + // location 0: position (xyz) + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, + sizeof(Vertex), + reinterpret_cast(offsetof(Vertex, x))); + // location 1: color (rgb) + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, + sizeof(Vertex), + reinterpret_cast(offsetof(Vertex, r))); + + vbo.release(); + vao.release(); +} + +void ReciprocalSpaceGLView::uploadBuffer(QOpenGLBuffer &vbo, + QOpenGLVertexArrayObject &vao, + const std::vector &data) { + vao.bind(); + vbo.bind(); + if (data.empty()) { + vbo.allocate(nullptr, 0); + } else { + vbo.allocate(data.data(), + static_cast(data.size() * sizeof(Vertex))); + } + vbo.release(); + vao.release(); +} + +// --------------------------------------------------------------------------- +// Mouse / wheel -> orbit camera +// --------------------------------------------------------------------------- + +void ReciprocalSpaceGLView::mousePressEvent(QMouseEvent *e) { + lastMousePos_ = e->pos(); +} + +void ReciprocalSpaceGLView::mouseMoveEvent(QMouseEvent *e) { + const QPoint delta = e->pos() - lastMousePos_; + lastMousePos_ = e->pos(); + + if (e->buttons() & Qt::LeftButton) { + yaw_ += delta.x() * 0.5f; + pitch_ += delta.y() * 0.5f; + pitch_ = qBound(-89.0f, pitch_, 89.0f); + update(); + } +} + +void ReciprocalSpaceGLView::wheelEvent(QWheelEvent *e) { + zoom_ *= (e->angleDelta().y() > 0) ? 0.9f : 1.1f; + zoom_ = qBound(1.0f, zoom_, 5000.0f); + update(); +} + +// ============================================================================ +// JFJochViewerReciprocalSpaceWindow +// ============================================================================ + JFJochViewerReciprocalSpaceWindow::JFJochViewerReciprocalSpaceWindow(QWidget *parent) : JFJochHelperWindow(parent) { setWindowTitle("Reciprocal space"); resize(800, 800); - // --- Widget layout ------------------------------------------------------- - auto *central = new QWidget(this); - auto *layout = new QVBoxLayout(central); + auto *central = new QWidget(this); + auto *layout = new QVBoxLayout(central); - view_ = new Qt3DExtras::Qt3DWindow(); - view_->defaultFrameGraph()->setClearColor(QColor(20, 20, 25)); - QWidget *container = QWidget::createWindowContainer(view_, central); - container->setMinimumSize(400, 400); - container->setFocusPolicy(Qt::StrongFocus); - layout->addWidget(container, 1); + glView_ = new ReciprocalSpaceGLView(central); + glView_->setMinimumSize(400, 400); + layout->addWidget(glView_, 1); auto *controls = new QHBoxLayout(); crystalFrameCheck = new QCheckBox("Crystal frame (angle = 0)", central); @@ -108,36 +239,15 @@ JFJochViewerReciprocalSpaceWindow::JFJochViewerReciprocalSpaceWindow(QWidget *pa setCentralWidget(central); connect(crystalFrameCheck, &QCheckBox::toggled, - this, &JFJochViewerReciprocalSpaceWindow::rebuildScene); + this, &JFJochViewerReciprocalSpaceWindow::rebuildGL); connect(showCellCheck, &QCheckBox::toggled, - this, &JFJochViewerReciprocalSpaceWindow::rebuildScene); + this, &JFJochViewerReciprocalSpaceWindow::rebuildGL); connect(clearButton, &QPushButton::clicked, this, [this] { spots_.clear(); - rebuildScene(); + rebuildGL(); }); - // --- Qt3D scene ---------------------------------------------------------- - root_ = new Qt3DCore::QEntity(); - - auto *camera = view_->camera(); - camera->lens()->setPerspectiveProjection(45.0f, 1.0f, 0.1f, 10000.0f); - camera->setPosition(QVector3D(0, 0, 80)); - camera->setViewCenter(QVector3D(0, 0, 0)); - - auto *lightEntity = new Qt3DCore::QEntity(root_); - auto *light = new Qt3DRender::QPointLight(lightEntity); - light->setIntensity(1.0f); - auto *lightTransform = new Qt3DCore::QTransform(lightEntity); - lightTransform->setTranslation(QVector3D(0, 0, 200)); - lightEntity->addComponent(light); - lightEntity->addComponent(lightTransform); - - auto *camController = new Qt3DExtras::QOrbitCameraController(root_); - camController->setCamera(camera); - - view_->setRootEntity(root_); - - rebuildScene(); + rebuildGL(); } // --------------------------------------------------------------------------- @@ -158,10 +268,11 @@ void JFJochViewerReciprocalSpaceWindow::datasetLoaded( if (!has_rotation_) crystalFrameCheck->setChecked(false); - rebuildScene(); + rebuildGL(); } -void JFJochViewerReciprocalSpaceWindow::imageLoaded(std::shared_ptr image) +void JFJochViewerReciprocalSpaceWindow::imageLoaded( + std::shared_ptr image) { if (!accumulateCheck->isChecked()) { spots_.clear(); @@ -169,13 +280,13 @@ void JFJochViewerReciprocalSpaceWindow::imageLoaded(std::shared_ptrDataset(); - const auto geom = dataset.experiment.GetDiffractionGeometry(); - const auto axis = dataset.experiment.GetGoniometer(); + const auto &dataset = image->Dataset(); + const auto geom = dataset.experiment.GetDiffractionGeometry(); + const auto axis = dataset.experiment.GetGoniometer(); const int64_t image_number = image->ImageData().number; std::optional back_rot; @@ -195,80 +306,102 @@ void JFJochViewerReciprocalSpaceWindow::imageLoaded(std::shared_ptr max_accumulated) - spots_.erase(spots_.begin()); + // Soft cap: no per-spot Qt objects here, so 200k is fine. + constexpr size_t max_accumulated = 200000; + if (spots_.size() > max_accumulated) + spots_.erase(spots_.begin(), + spots_.begin() + + static_cast(spots_.size() - max_accumulated)); if (image->ImageData().indexing_lattice) indexed_lattice_ = image->ImageData().indexing_lattice; - rebuildScene(); + rebuildGL(); } void JFJochViewerReciprocalSpaceWindow::setSpotColor(QColor input) { if (!input.isValid()) return; spot_color = input; - rebuildScene(); + rebuildGL(); } void JFJochViewerReciprocalSpaceWindow::setFeatureColor(QColor input) { if (!input.isValid()) return; indexed_color = input; - rebuildScene(); + rebuildGL(); } // --------------------------------------------------------------------------- -// Private helpers +// Private // --------------------------------------------------------------------------- -QColor JFJochViewerReciprocalSpaceWindow::spotColorFor(bool indexed, bool ice_ring) const { +QColor JFJochViewerReciprocalSpaceWindow::spotColorFor(bool indexed, + bool ice_ring) const { if (indexed) return indexed_color; if (ice_ring) return ice_ring_color; return spot_color; } -void JFJochViewerReciprocalSpaceWindow::rebuildScene() { - // Delete the previous scene content (axes, cell vectors, all spot balls). - // root_ itself stays alive — only its dynamic child is replaced. - if (sceneContent_) { - delete sceneContent_; // Qt parent-child: also deletes all children - sceneContent_ = nullptr; - } - - sceneContent_ = new Qt3DCore::QEntity(root_); - - addAxes(); - if (showCellCheck->isChecked() && indexed_lattice_) - addCellVectors(); - addSpots(); -} - -void JFJochViewerReciprocalSpaceWindow::addAxes() { - const float len = 10.0f; - const float thickness = 0.05f; - AddVector(sceneContent_, QVector3D(len, 0, 0), QColor(120, 60, 60), thickness); - AddVector(sceneContent_, QVector3D(0, len, 0), QColor(60, 120, 60), thickness); - AddVector(sceneContent_, QVector3D(0, 0, len), QColor(60, 60, 120), thickness); -} - -void JFJochViewerReciprocalSpaceWindow::addCellVectors() { - const Coord astar = indexed_lattice_->Astar(); - const Coord bstar = indexed_lattice_->Bstar(); - const Coord cstar = indexed_lattice_->Cstar(); - - const float thickness = 0.15f; - AddVector(sceneContent_, QVector3D(astar.x, astar.y, astar.z) * scene_scale_, Qt::red, thickness); - AddVector(sceneContent_, QVector3D(bstar.x, bstar.y, bstar.z) * scene_scale_, Qt::green, thickness); - AddVector(sceneContent_, QVector3D(cstar.x, cstar.y, cstar.z) * scene_scale_, Qt::blue, thickness); -} - -void JFJochViewerReciprocalSpaceWindow::addSpots() { +void JFJochViewerReciprocalSpaceWindow::rebuildGL() { const bool crystal_frame = crystalFrameCheck->isChecked() && has_rotation_; + // --- Spots --------------------------------------------------------------- + std::vector spotVerts; + spotVerts.reserve(spots_.size()); + + // Precompute the three possible colors as floats — avoids QColor in the loop. + auto toF = [](const QColor &c, float &r, float &g, float &b) { + r = float(c.redF()); g = float(c.greenF()); b = float(c.blueF()); + }; + float sr, sg, sb, ir, ig, ib, xr, xg, xb; + toF(spot_color, sr, sg, sb); + toF(indexed_color, ir, ig, ib); + toF(ice_ring_color, xr, xg, xb); + for (const auto &spot : spots_) { const Coord &c = crystal_frame ? spot.recip_crystal : spot.recip_lab; - const QVector3D pos(c.x * scene_scale_, c.y * scene_scale_, c.z * scene_scale_); - AddBall(sceneContent_, pos, spotColorFor(spot.indexed, spot.ice_ring)); + float r, g, b; + if (spot.indexed) { r = ir; g = ig; b = ib; } + else if (spot.ice_ring) { r = xr; g = xg; b = xb; } + else { r = sr; g = sg; b = sb; } + spotVerts.push_back({ c.x * scene_scale_, + c.y * scene_scale_, + c.z * scene_scale_, + r, g, b }); } + + // --- Lines: axes + optional cell vectors --------------------------------- + // Each segment = 2 vertices (GL_LINES draws pairs). + std::vector lineVerts; + + auto addLine = [&](QVector3D a, QVector3D b, QColor col) { + const float r = float(col.redF()), + g = float(col.greenF()), + bv = float(col.blueF()); + lineVerts.push_back({ a.x(), a.y(), a.z(), r, g, bv }); + lineVerts.push_back({ b.x(), b.y(), b.z(), r, g, bv }); + }; + + // 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)); + + // Reciprocal cell vectors + if (showCellCheck->isChecked() && indexed_lattice_) { + auto addCell = [&](const Coord &v, QColor col) { + addLine({0, 0, 0}, + { v.x * scene_scale_, + v.y * scene_scale_, + v.z * scene_scale_ }, + col); + }; + addCell(indexed_lattice_->Astar(), Qt::red); + addCell(indexed_lattice_->Bstar(), Qt::green); + addCell(indexed_lattice_->Cstar(), Qt::blue); + } + + glView_->setSpots(spotVerts); + glView_->setLines(lineVerts); } \ No newline at end of file diff --git a/viewer/windows/JFJochViewerReciprocalSpaceWindow.h b/viewer/windows/JFJochViewerReciprocalSpaceWindow.h index 6654358f..06e9da28 100644 --- a/viewer/windows/JFJochViewerReciprocalSpaceWindow.h +++ b/viewer/windows/JFJochViewerReciprocalSpaceWindow.h @@ -1,79 +1,116 @@ // SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute // SPDX-License-Identifier: GPL-3.0-only - #pragma once +#include #include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include + #include +#include +#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "JFJochHelperWindow.h" // your existing base class +#include "../../common/CrystalLattice.h" // Coord, RotMatrix, etc. -#include "JFJochHelperWindow.h" -#include "../../common/Coord.h" +// Forward declarations from your codebase +class JFJochReaderDataset; +class JFJochReaderImage; +// --------------------------------------------------------------------------- +// ReciprocalSpaceGLView – the actual OpenGL viewport +// --------------------------------------------------------------------------- +class ReciprocalSpaceGLView : public QOpenGLWidget, + protected QOpenGLFunctions_3_3_Core { + Q_OBJECT +public: + explicit ReciprocalSpaceGLView(QWidget *parent = nullptr); + ~ReciprocalSpaceGLView() override; + + struct Vertex { float x, y, z, r, g, b; }; + + // Called by the outer window whenever data changes. + void setSpots(const std::vector &spots); + void setLines(const std::vector &lines); // axes + cell vectors + +protected: + void initializeGL() override; + void resizeGL(int w, int h) override; + void paintGL() override; + + void mousePressEvent(QMouseEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void wheelEvent(QWheelEvent *e) override; + +private: + void uploadBuffer(QOpenGLBuffer &vbo, QOpenGLVertexArrayObject &vao, + const std::vector &data); + void setupVAO(QOpenGLVertexArrayObject &vao, QOpenGLBuffer &vbo); + + QOpenGLShaderProgram shader_; + + QOpenGLVertexArrayObject spotsVAO_; + QOpenGLBuffer spotsVBO_; + int spotsCount_ = 0; + + QOpenGLVertexArrayObject linesVAO_; + QOpenGLBuffer linesVBO_; + int linesCount_ = 0; + + // Camera state + QMatrix4x4 proj_; + float yaw_ = 0.0f; // degrees + float pitch_ = 20.0f; // degrees + float zoom_ = 80.0f; // distance from origin + QPoint lastMousePos_; +}; + +// --------------------------------------------------------------------------- +// JFJochViewerReciprocalSpaceWindow – the outer QMainWindow (unchanged API) +// --------------------------------------------------------------------------- class JFJochViewerReciprocalSpaceWindow : public JFJochHelperWindow { Q_OBJECT public: explicit JFJochViewerReciprocalSpaceWindow(QWidget *parent = nullptr); public slots: - // Keep 3D colors in sync with the 2D image - void setSpotColor(QColor input); - void setFeatureColor(QColor input); - - // Reset everything (called on new dataset) - void datasetLoaded(std::shared_ptr in_dataset) override; - // Add spots from currently open image (accumulating) - void imageLoaded(std::shared_ptr image) override; - -private slots: - void rebuildScene(); + void datasetLoaded(std::shared_ptr dataset); + void imageLoaded(std::shared_ptr image); + void setSpotColor(QColor color); + void setFeatureColor(QColor color); private: struct AccumulatedSpot { - Coord recip_lab; // reciprocal coord in lab frame at the image's angle - Coord recip_crystal; // reciprocal coord rotated back to angle = 0 - bool indexed = false; - bool ice_ring = false; + Coord recip_lab; + Coord recip_crystal; + bool indexed = false; + bool ice_ring = false; }; - void clearScene(); - void rebuildSpots(); // refresh the point-cloud buffers - void addCellVectors(); - void addAxes(); - QColor spotColorFor(bool indexed, bool ice_ring) const; + void rebuildGL(); // rebuilds both spot and line vertex data - Qt3DExtras::Qt3DWindow *view_ = nullptr; - Qt3DCore::QEntity *root_ = nullptr; - Qt3DCore::QEntity *sceneContent_ = nullptr; // re-created when cell/frame changes + // UI + ReciprocalSpaceGLView *glView_ = nullptr; + QCheckBox *crystalFrameCheck = nullptr; + QCheckBox *showCellCheck = nullptr; + QCheckBox *accumulateCheck = nullptr; - QCheckBox *accumulateCheck = nullptr; + // Data + std::vector spots_; + std::optional indexed_lattice_; + bool has_rotation_ = false; + float scene_scale_ = 100.0f; - Qt3DExtras::QOrbitCameraController *camController = nullptr; - - std::vector spots_; - std::optional indexed_lattice_; - bool has_rotation_ = false; - - // UI options - QCheckBox *crystalFrameCheck = nullptr; // angle=0 frame vs current - QCheckBox *showCellCheck = nullptr; - - // Colors matching the diffraction image + // Colors QColor spot_color = Qt::green; QColor indexed_color = Qt::magenta; // feature_color in image QColor ice_ring_color = Qt::cyan; - - float scene_scale_ = 100.0f; // recip Å^-1 are small; scale up for rendering - void addSpots(); -}; \ No newline at end of file +};