v2.0.0
Loading...
Searching...
No Matches
raypicker.cpp
Go to the documentation of this file.
1//=============================================================================================================
12
13//=============================================================================================================
14// INCLUDES
15//=============================================================================================================
16
17#include "raypicker.h"
22
23#include <QVector4D>
24#include <limits>
25
26//=============================================================================================================
27// DEFINE MEMBER METHODS
28//=============================================================================================================
29
30bool RayPicker::unproject(const QPoint &screenPos,
31 const QRect &paneRect,
32 const QMatrix4x4 &pvm,
33 QVector3D &rayOrigin,
34 QVector3D &rayDir)
35{
36 bool invertible = false;
37 QMatrix4x4 invPVM = pvm.inverted(&invertible);
38 if (!invertible) return false;
39
40 const float localX = static_cast<float>(screenPos.x() - paneRect.x());
41 const float localY = static_cast<float>(screenPos.y() - paneRect.y());
42 const float paneW = static_cast<float>(std::max(1, paneRect.width()));
43 const float paneH = static_cast<float>(std::max(1, paneRect.height()));
44
45 const float ndcX = (2.0f * localX) / paneW - 1.0f;
46 const float ndcY = 1.0f - (2.0f * localY) / paneH;
47
48 QVector4D vNear(ndcX, ndcY, -1.0f, 1.0f);
49 QVector4D vFar (ndcX, ndcY, 1.0f, 1.0f);
50
51 QVector4D pNear = invPVM * vNear;
52 QVector4D pFar = invPVM * vFar;
53 pNear /= pNear.w();
54 pFar /= pFar.w();
55
56 rayOrigin = pNear.toVector3D();
57 rayDir = (pFar.toVector3D() - pNear.toVector3D()).normalized();
58 return true;
59}
60
61//=============================================================================================================
62
63PickResult RayPicker::pick(const QVector3D &rayOrigin,
64 const QVector3D &rayDir,
65 const SubView &subView,
66 const QMap<QString, std::shared_ptr<BrainSurface>> &surfaces,
67 const QMap<const QStandardItem*, std::shared_ptr<BrainSurface>> &itemSurfaceMap,
68 const QMap<const QStandardItem*, std::shared_ptr<DipoleObject>> &itemDipoleMap)
69{
70 PickResult result;
71 float closestDist = std::numeric_limits<float>::max();
72
73 // ── Test surfaces ──────────────────────────────────────────────────
74 for (auto it = surfaces.cbegin(); it != surfaces.cend(); ++it) {
75 const QString &key = it.key();
76 const auto &surf = it.value();
77
78 if (!surf->isVisible()) continue;
79 if (!subView.shouldRenderSurface(key)) continue;
80 if (key.startsWith("srcsp_")) continue; // skip source space for picking
81
82 const bool isSensor = key.startsWith("sens_");
83 const bool isBem = key.startsWith("bem_");
84 const bool isDig = key.startsWith("dig_");
85
86 // Brain surfaces: only pick if matching active surface type
87 if (!isSensor && !isBem && !isDig) {
88 if (!subView.matchesSurfaceType(key)) continue;
89 }
90
91 float dist = 0.0f;
92 int vertexIdx = -1;
93 if (surf->intersects(rayOrigin, rayDir, dist, vertexIdx)) {
94 if (dist < closestDist) {
95 closestDist = dist;
96 result.hit = true;
97 result.distance = dist;
98 result.hitPoint = rayOrigin + dist * rayDir;
99 result.vertexIndex = vertexIdx;
100 result.surfaceKey = key;
101 result.isDipole = false;
102 result.dipoleIndex = -1;
103
104 // Reverse lookup: find tree item for this surface
105 result.item = nullptr;
106 for (auto i = itemSurfaceMap.cbegin(); i != itemSurfaceMap.cend(); ++i) {
107 if (i.value() == surf) {
108 result.item = const_cast<QStandardItem*>(i.key());
109 break;
110 }
111 }
112
113 // FsAnnotation info
114 if (result.item && itemSurfaceMap.contains(result.item)) {
115 result.regionName = itemSurfaceMap[result.item]->getAnnotationLabel(vertexIdx);
116 result.regionId = itemSurfaceMap[result.item]->getAnnotationLabelId(vertexIdx);
117 } else {
118 result.regionName.clear();
119 result.regionId = -1;
120 }
121 }
122 }
123 }
124
125 // ── Test dipoles ───────────────────────────────────────────────────
126 for (auto it = itemDipoleMap.cbegin(); it != itemDipoleMap.cend(); ++it) {
127 if (!subView.visibility.dipoles) continue;
128 if (!it.value()->isVisible()) continue;
129
130 float dist = 0.0f;
131 int dipIdx = it.value()->intersect(rayOrigin, rayDir, dist);
132 if (dipIdx != -1 && dist < closestDist) {
133 closestDist = dist;
134 result.hit = true;
135 result.distance = dist;
136 result.hitPoint = rayOrigin + dist * rayDir;
137 result.item = const_cast<QStandardItem*>(it.key());
138 result.surfaceKey.clear();
139 result.vertexIndex = dipIdx;
140 result.isDipole = true;
141 result.dipoleIndex = dipIdx;
142 result.regionName.clear();
143 result.regionId = -1;
144 }
145 }
146
147 return result;
148}
149
150//=============================================================================================================
151
152QString RayPicker::buildLabel(const PickResult &result,
153 const QMap<const QStandardItem*, std::shared_ptr<BrainSurface>> &itemSurfaceMap,
154 const QMap<QString, std::shared_ptr<BrainSurface>> &surfaces)
155{
156 if (!result.hit) return QString();
157
158 const QString &key = result.surfaceKey;
159
160 // ── Brain surface with annotation ──────────────────────────────────
161 if (!result.regionName.isEmpty()) {
162 QString hemi;
163 if (key.startsWith("lh")) hemi = "lh";
164 else if (key.startsWith("rh")) hemi = "rh";
165
166 return hemi.isEmpty()
167 ? QString("Region: %1").arg(result.regionName)
168 : QString("Region: %1 (%2)").arg(result.regionName, hemi);
169 }
170
171 // ── Dipole ─────────────────────────────────────────────────────────
172 if (result.isDipole) {
173 QString name = result.item ? result.item->text() : QStringLiteral("Dipole");
174 return QString("%1 (Dipole %2)").arg(name).arg(result.dipoleIndex);
175 }
176
177 // ── Sensor/BEM/Digitizer/Helmet ────────────────────────────────────
178 if (key.startsWith("sens_surface_meg")) {
179 return QStringLiteral("MEG Helmet");
180 }
181 if (key.startsWith("sens_meg_")) {
182 return QString("MEG: %1").arg(result.item ? result.item->text() : key);
183 }
184 if (key.startsWith("sens_eeg_")) {
185 return QString("EEG: %1").arg(result.item ? result.item->text() : key);
186 }
187 if (key.startsWith("dig_")) {
188 // Resolve individual point from batched mesh
189 QString pointName;
190 if (result.item && result.vertexIndex >= 0) {
191 AbstractTreeItem *abs = dynamic_cast<AbstractTreeItem*>(result.item);
193 auto *digItem = static_cast<DigitizerTreeItem*>(abs);
194 constexpr int vertsPerSphere = 42;
195 int ptIdx = result.vertexIndex / vertsPerSphere;
196 const QStringList &names = digItem->pointNames();
197 if (ptIdx >= 0 && ptIdx < names.size())
198 pointName = names[ptIdx];
199 }
200 }
201 QString category = key.mid(4);
202 if (!category.isEmpty()) category[0] = category[0].toUpper();
203 return pointName.isEmpty()
204 ? QString("Digitizer (%1)").arg(category)
205 : QString("Digitizer: %1 (%2)").arg(pointName, category);
206 }
207 if (key.startsWith("bem_")) {
208 QString compartment = key.mid(4);
209 if (!compartment.isEmpty()) compartment[0] = compartment[0].toUpper();
210 compartment.replace("_", " ");
211 return QString("BEM: %1").arg(compartment);
212 }
213
214 // ── Hemisphere fallback ────────────────────────────────────────────
215 if (key.startsWith("lh_")) return QStringLiteral("Left Hemisphere");
216 if (key.startsWith("rh_")) return QStringLiteral("Right Hemisphere");
217
218 return QString();
219}
220
221//=============================================================================================================
222
224{
225 // Delegated to the static builder in RayPicker; this method is a
226 // convenience wrapper when the caller doesn't have the surface maps.
227 if (!hit) return QString();
228
229 if (!regionName.isEmpty()) {
230 QString hemi;
231 if (surfaceKey.startsWith("lh")) hemi = "lh";
232 else if (surfaceKey.startsWith("rh")) hemi = "rh";
233 return hemi.isEmpty()
234 ? QString("Region: %1").arg(regionName)
235 : QString("Region: %1 (%2)").arg(regionName, hemi);
236 }
237
238 if (isDipole) {
239 return QString("Dipole %1").arg(dipoleIndex);
240 }
241
242 return surfaceKey;
243}
Renderable cortical / BEM mesh with interleaved vertex attributes and Qt-RHI buffer management.
Instanced-arrow renderable for fitted equivalent current dipoles, driven by QRhi instancing.
Tree item holding a single category of digitizer points rendered as a batched-sphere mesh.
Base QStandardItem with check-state, visibility, transform, colour and alpha roles shared by every di...
Mouse-ray vs scene-object intersection for picking dipoles, electrodes and surfaces.
Viewport subdivision holding its own camera, projection, and scissor rectangle.
Definition viewstate.h:139
bool matchesSurfaceType(const QString &key) const
ViewVisibilityProfile visibility
Definition viewstate.h:145
bool shouldRenderSurface(const QString &key) const
Result of a ray–mesh intersection test containing the hit point, triangle index, and distance.
Definition raypicker.h:55
int dipoleIndex
Index within the dipole set.
Definition raypicker.h:65
int vertexIndex
Vertex or element index at hit.
Definition raypicker.h:62
bool hit
True if something was hit.
Definition raypicker.h:56
QString surfaceKey
FsSurface map key of the hit surface.
Definition raypicker.h:61
QVector3D hitPoint
World-space intersection point.
Definition raypicker.h:58
QString displayLabel() const
QString regionName
FsAnnotation region label (if available).
Definition raypicker.h:68
QStandardItem * item
Tree item that was hit (nullable).
Definition raypicker.h:60
float distance
Distance along ray to hit point.
Definition raypicker.h:57
bool isDipole
True if a dipole was hit.
Definition raypicker.h:64
int regionId
FsAnnotation label ID.
Definition raypicker.h:69
static bool unproject(const QPoint &screenPos, const QRect &paneRect, const QMatrix4x4 &pvm, QVector3D &rayOrigin, QVector3D &rayDir)
Definition raypicker.cpp:30
static QString buildLabel(const PickResult &result, const QMap< const QStandardItem *, std::shared_ptr< BrainSurface > > &itemSurfaceMap, const QMap< QString, std::shared_ptr< BrainSurface > > &surfaces)
static PickResult pick(const QVector3D &rayOrigin, const QVector3D &rayDir, const SubView &subView, const QMap< QString, std::shared_ptr< BrainSurface > > &surfaces, const QMap< const QStandardItem *, std::shared_ptr< BrainSurface > > &itemSurfaceMap, const QMap< const QStandardItem *, std::shared_ptr< DipoleObject > > &itemDipoleMap)
Definition raypicker.cpp:63
Base tree item providing check-state, visibility, and data-role storage for all 3-D scene items.
static constexpr int itemTypeId(ItemType type)
int type() const override
Digitizer point group tree item.