JFJochViewerReciprocalSpaceWindow: OpenGL version, seems to work well
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 9m59s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 10m38s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 11m42s
Build Packages / build:rpm (rocky8) (push) Successful in 12m20s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 12m28s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 13m41s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 13m42s
Build Packages / XDS test (durin plugin) (push) Successful in 7m14s
Build Packages / Generate python client (push) Successful in 37s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 9m25s
Build Packages / Create release (push) Skipped
Build Packages / Build documentation (push) Successful in 45s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 10m58s
Build Packages / XDS test (neggia plugin) (push) Successful in 8m8s
Build Packages / build:rpm (rocky9) (push) Successful in 12m8s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 8m42s
Build Packages / DIALS test (push) Successful in 11m20s
Build Packages / Unit tests (push) Successful in 57m41s

This commit is contained in:
2026-05-31 19:59:13 +02:00
parent b66fa9e5c6
commit baeef1960e
3 changed files with 364 additions and 194 deletions
+2 -2
View File
@@ -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)
@@ -3,91 +3,222 @@
#include "JFJochViewerReciprocalSpaceWindow.h"
#include <QWidget>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPushButton>
#include <QSurfaceFormat>
#include <QtMath>
#include <Qt3DCore/QEntity>
#include <Qt3DCore/QTransform>
#include <Qt3DExtras/Qt3DWindow>
#include <Qt3DExtras/QOrbitCameraController>
#include <Qt3DExtras/QSphereMesh>
#include <Qt3DExtras/QCylinderMesh>
#include <Qt3DExtras/QPhongMaterial>
#include <Qt3DRender/QCamera>
#include <Qt3DRender/QPointLight>
#include <Qt3DExtras/QForwardRenderer>
// ============================================================================
// 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<Vertex> &spots) {
makeCurrent();
uploadBuffer(spotsVBO_, spotsVAO_, spots);
spotsCount_ = static_cast<int>(spots.size());
doneCurrent();
update();
}
void ReciprocalSpaceGLView::setLines(const std::vector<Vertex> &lines) {
makeCurrent();
uploadBuffer(linesVBO_, linesVAO_, lines);
linesCount_ = static_cast<int>(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<void *>(offsetof(Vertex, x)));
// location 1: color (rgb)
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE,
sizeof(Vertex),
reinterpret_cast<void *>(offsetof(Vertex, r)));
vbo.release();
vao.release();
}
void ReciprocalSpaceGLView::uploadBuffer(QOpenGLBuffer &vbo,
QOpenGLVertexArrayObject &vao,
const std::vector<Vertex> &data) {
vao.bind();
vbo.bind();
if (data.empty()) {
vbo.allocate(nullptr, 0);
} else {
vbo.allocate(data.data(),
static_cast<int>(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<const JFJochReaderImage> image)
void JFJochViewerReciprocalSpaceWindow::imageLoaded(
std::shared_ptr<const JFJochReaderImage> image)
{
if (!accumulateCheck->isChecked()) {
spots_.clear();
@@ -169,13 +280,13 @@ void JFJochViewerReciprocalSpaceWindow::imageLoaded(std::shared_ptr<const JFJoch
}
if (!image) {
rebuildScene();
rebuildGL();
return;
}
const auto &dataset = image->Dataset();
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<RotMatrix> back_rot;
@@ -195,80 +306,102 @@ void JFJochViewerReciprocalSpaceWindow::imageLoaded(std::shared_ptr<const JFJoch
spots_.emplace_back(acc);
}
// Soft cap: drop oldest when accumulation grows too large.
constexpr size_t max_accumulated = 5000; // sphere-per-spot: keep this sane
while (spots_.size() > 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<std::ptrdiff_t>(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<ReciprocalSpaceGLView::Vertex> 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<ReciprocalSpaceGLView::Vertex> 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);
}
@@ -1,79 +1,116 @@
// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#pragma once
#include <QMainWindow>
#include <QCheckBox>
#include <QVector3D>
#include <optional>
#include <QOpenGLWidget>
#include <QOpenGLFunctions_3_3_Core>
#include <QOpenGLShaderProgram>
#include <QOpenGLBuffer>
#include <QOpenGLVertexArrayObject>
#include <QMatrix4x4>
#include <QMouseEvent>
#include <QWheelEvent>
#include <vector>
#include <memory>
#include <optional>
#include <Qt3DExtras/Qt3DWindow>
#include <Qt3DCore/QEntity>
#include <Qt3DExtras/QOrbitCameraController>
#include <Qt3DCore/QGeometry>
#include <Qt3DCore/QAttribute>
#include <Qt3DCore/QBuffer>
#include <Qt3DRender/QGeometryRenderer>
#include <Qt3DRender/QMaterial>
#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<Vertex> &spots);
void setLines(const std::vector<Vertex> &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<Vertex> &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<const JFJochReaderDataset> in_dataset) override;
// Add spots from currently open image (accumulating)
void imageLoaded(std::shared_ptr<const JFJochReaderImage> image) override;
private slots:
void rebuildScene();
void datasetLoaded(std::shared_ptr<const JFJochReaderDataset> dataset);
void imageLoaded(std::shared_ptr<const JFJochReaderImage> 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<AccumulatedSpot> spots_;
std::optional<CrystalLattice> indexed_lattice_;
bool has_rotation_ = false;
float scene_scale_ = 100.0f;
Qt3DExtras::QOrbitCameraController *camController = nullptr;
std::vector<AccumulatedSpot> spots_;
std::optional<CrystalLattice> 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();
};
};