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
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:
@@ -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();
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user