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