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