v2.0.0
Loading...
Searching...
No Matches
brainsurface.cpp
Go to the documentation of this file.
1//=============================================================================================================
34
35//=============================================================================================================
36// INCLUDES
37//=============================================================================================================
38
39#include "brainsurface.h"
40
41#include <rhi/qrhi.h>
42
43#include <set>
44
45//=============================================================================================================
46// PIMPL
47//=============================================================================================================
48
50{
51 std::unique_ptr<QRhiBuffer> vertexBuffer;
52 std::unique_ptr<QRhiBuffer> indexBuffer;
53 bool dirty = true;
54};
55
56
57//=============================================================================================================
58// DEFINE MEMBER METHODS
59//=============================================================================================================
60
61
62//=============================================================================================================
63
65 : m_gpu(std::make_unique<GpuBuffers>())
66{
67}
68
69//=============================================================================================================
70
72
73//=============================================================================================================
74
75QRhiBuffer* BrainSurface::vertexBuffer() const { return m_gpu->vertexBuffer.get(); }
76QRhiBuffer* BrainSurface::indexBuffer() const { return m_gpu->indexBuffer.get(); }
77
78//=============================================================================================================
79
81{
82 m_vertexData.clear();
83 m_indexData.clear();
84
85 const Eigen::MatrixXf &rr = surf.rr();
86 const Eigen::MatrixXf &nn = surf.nn();
87 const Eigen::MatrixXi &tris = surf.tris();
88 const Eigen::VectorXf &curv = surf.curv();
89 m_curvature.resize(curv.size());
90 for(int i=0; i<curv.size(); ++i) m_curvature[i] = curv[i];
91
92 // Populate vertex data
93 m_vertexData.reserve(rr.rows());
94
95 for (int i = 0; i < rr.rows(); ++i) {
96 VertexData v;
97 v.pos = QVector3D(rr(i, 0), rr(i, 1), rr(i, 2));
98 v.norm = QVector3D(nn(i, 0), nn(i, 1), nn(i, 2));
99 v.color = 0xFFFFFFFF; // Default white (overwritten by updateVertexColors)
100 v.colorAnnotation = 0x00000000; // No annotation yet
101 m_vertexData.append(v);
102 }
103
104 m_indexData.reserve(tris.rows() * 3);
105 for (int i = 0; i < tris.rows(); ++i) {
106 m_indexData.append(tris(i, 0));
107 m_indexData.append(tris(i, 1));
108 m_indexData.append(tris(i, 2));
109 }
110 m_indexCount = m_indexData.size();
111
112 m_gpu->dirty = true;
113 m_bAABBDirty = true;
114
115 // Initial coloring based on current visualization mode
116 updateVertexColors();
117
118 m_originalVertexData = m_vertexData;
119}
120
121//=============================================================================================================
122
123//=============================================================================================================
124
125void BrainSurface::fromBemSurface(const MNELIB::MNEBemSurface &surf, const QColor &color)
126{
127 m_vertexData.clear();
128 m_indexData.clear();
129 m_curvature.clear(); // BEM has no curvature info usually
130
131 int nVerts = surf.rr.rows();
132 m_vertexData.reserve(nVerts);
133
134 // Compute normals if missing
135 Eigen::MatrixX3f nn = surf.nn;
136 if (nn.rows() != nVerts) {
137 nn = FSLIB::Surface::compute_normals(Eigen::MatrixX3f(surf.rr), Eigen::MatrixX3i(surf.itris));
138 }
139
140 m_defaultColor = color;
141 m_baseColor = color;
142 uint32_t colorVal = packABGR(color.red(), color.green(), color.blue(), color.alpha());
143
144 for (int i = 0; i < nVerts; ++i) {
145 VertexData v;
146 v.pos = QVector3D(surf.rr(i, 0), surf.rr(i, 1), surf.rr(i, 2));
147 v.norm = QVector3D(nn(i, 0), nn(i, 1), nn(i, 2));
148 v.color = colorVal;
149 v.colorAnnotation = 0x00000000;
150 m_vertexData.append(v);
151 }
152
153 int nTris = surf.itris.rows();
154 m_indexData.reserve(nTris * 3);
155 for (int i = 0; i < nTris; ++i) {
156 m_indexData.append(surf.itris(i, 0));
157 m_indexData.append(surf.itris(i, 1));
158 m_indexData.append(surf.itris(i, 2));
159 }
160 m_indexCount = m_indexData.size();
161
162 m_originalVertexData = m_vertexData;
163 m_gpu->dirty = true;
164}
165
166void BrainSurface::createFromData(const Eigen::MatrixX3f &vertices, const Eigen::MatrixX3i &triangles, const QColor &color)
167{
168 // Compute spherical normals (legacy behavior / fallback)
169 // Note: detailed normals should be passed via the overload for specific shapes
170 Eigen::MatrixX3f normals(vertices.rows(), 3);
171 for(int i=0; i<vertices.rows(); ++i) {
172 QVector3D p(vertices(i, 0), vertices(i, 1), vertices(i, 2));
173 QVector3D n = p.normalized();
174 normals(i, 0) = n.x();
175 normals(i, 1) = n.y();
176 normals(i, 2) = n.z();
177 }
178 createFromData(vertices, normals, triangles, color);
179}
180
181void BrainSurface::createFromData(const Eigen::MatrixX3f &vertices, const Eigen::MatrixX3f &normals, const Eigen::MatrixX3i &triangles, const QColor &color)
182{
183 m_vertexData.clear();
184 m_indexData.clear();
185 m_curvature.clear();
186
187 int nVerts = vertices.rows();
188 m_vertexData.reserve(nVerts);
189
190 m_defaultColor = color;
191 m_baseColor = color;
192 uint32_t colorVal = packABGR(color.red(), color.green(), color.blue(), color.alpha());
193
194 for (int i = 0; i < nVerts; ++i) {
195 VertexData v;
196 v.pos = QVector3D(vertices(i, 0), vertices(i, 1), vertices(i, 2));
197 v.norm = QVector3D(normals(i, 0), normals(i, 1), normals(i, 2));
198 v.color = colorVal;
199 v.colorAnnotation = 0x00000000;
200 m_vertexData.append(v);
201 }
202
203 int nTris = triangles.rows();
204 m_indexData.reserve(nTris * 3);
205 for (int i = 0; i < nTris; ++i) {
206 m_indexData.append(triangles(i, 0));
207 m_indexData.append(triangles(i, 1));
208 m_indexData.append(triangles(i, 2));
209 }
210 m_indexCount = m_indexData.size();
211
212 m_originalVertexData = m_vertexData;
213 m_gpu->dirty = true;
214}
215
216//=============================================================================================================
217
218Eigen::MatrixX3f BrainSurface::vertexPositions() const
219{
220 Eigen::MatrixX3f rr(m_vertexData.size(), 3);
221 for (int i = 0; i < m_vertexData.size(); ++i) {
222 rr(i, 0) = m_vertexData[i].pos.x();
223 rr(i, 1) = m_vertexData[i].pos.y();
224 rr(i, 2) = m_vertexData[i].pos.z();
225 }
226 return rr;
227}
228
229//=============================================================================================================
230
231Eigen::MatrixX3f BrainSurface::vertexNormals() const
232{
233 Eigen::MatrixX3f nn(m_vertexData.size(), 3);
234 for (int i = 0; i < m_vertexData.size(); ++i) {
235 nn(i, 0) = m_vertexData[i].norm.x();
236 nn(i, 1) = m_vertexData[i].norm.y();
237 nn(i, 2) = m_vertexData[i].norm.z();
238 }
239 return nn;
240}
241
242//=============================================================================================================
243
244bool BrainSurface::loadAnnotation(const QString &path)
245{
246 if (!FSLIB::Annotation::read(path, m_annotation)) {
247 qWarning() << "BrainSurface: Failed to load annotation from" << path;
248 return false;
249 }
250 m_hasAnnotation = true;
251 updateVertexColors();
252 m_gpu->dirty = true;
253 return true;
254}
255
257{
258 m_annotation = annotation;
259 m_hasAnnotation = true;
260 updateVertexColors();
261 m_gpu->dirty = true;
262}
263
264
265//=============================================================================================================
266
267void BrainSurface::setVisible(bool visible)
268{
269 m_visible = visible;
270}
271
272//=============================================================================================================
273
275{
276 if (m_visMode == mode)
277 return;
278
279 m_visMode = mode;
280
281 // Scientific and SourceEstimate both read from the primary colour
282 // channel (v_color). When switching between them the vertex buffer
283 // must be refreshed so the channel holds curvature grays (Scientific)
284 // or STC colours (SourceEstimate).
285 updateVertexColors();
286 m_gpu->dirty = true;
287}
288
289//=============================================================================================================
290
291void BrainSurface::applySourceEstimateColors(const QVector<uint32_t> &colors)
292{
293 m_visMode = ModeSourceEstimate;
294 m_stcColors = colors;
295
296 // Write STC colours into the primary color channel
297 for (int i = 0; i < qMin(colors.size(), m_vertexData.size()); ++i) {
298 m_vertexData[i].color = colors[i];
299 }
300
301 m_gpu->dirty = true;
302}
303
304//=============================================================================================================
305
307{
308 m_baseColor = useDefault ? m_defaultColor : Qt::white;
309 updateVertexColors();
310}
311
312void BrainSurface::updateVertexColors()
313{
314 // ── 1. Populate the primary "color" channel.
315 // Brain surfaces (with curvature): curvature-based gray.
316 // Non-brain surfaces (BEM, sensors, etc.): use m_baseColor.
317 // The shader uses this for Scientific mode and as a lighting base;
318 // in Surface mode the shader overrides with white for brain tissue.
319 if (!m_curvature.isEmpty() && m_curvature.size() == m_vertexData.size()) {
320 for (int i = 0; i < m_vertexData.size(); ++i) {
321 const uint32_t val = (m_curvature[i] > 0.0f) ? 0x40u : 0xAAu;
322 m_vertexData[i].color = packABGR(val, val, val);
323 }
324 } else {
325 // Use the base colour (set via setUseDefaultColor / fromBemSurface).
326 // This preserves BEM Red/Green/Blue colours and sensor colours.
327 const uint32_t baseVal = packABGR(m_baseColor.red(), m_baseColor.green(),
328 m_baseColor.blue(), m_baseColor.alpha());
329 for (int i = 0; i < m_vertexData.size(); ++i) {
330 m_vertexData[i].color = baseVal;
331 }
332 }
333
334 // Always keep STC colours in the primary colour channel when available.
335 // Each viewport selects the overlay mode via a per-draw shader uniform
336 // (overlayMode), so the vertex buffer must hold STC data for any
337 // viewport that shows Source Estimate, regardless of this surface's
338 // m_visMode. Annotation data lives in a separate vertex attribute
339 // (colorAnnotation) and is unaffected.
340 if (!m_stcColors.isEmpty()) {
341 for (int i = 0; i < qMin(m_stcColors.size(), m_vertexData.size()); ++i) {
342 m_vertexData[i].color = m_stcColors[i];
343 }
344 }
345
346 // ── 2. Populate colorAnnotation from loaded annotation data.
347 for (auto &v : m_vertexData) {
348 v.colorAnnotation = 0x00000000;
349 }
350
351 if (m_hasAnnotation && !m_vertexData.isEmpty()) {
352 const Eigen::VectorXi &vertices = m_annotation.getVertices();
353 const Eigen::VectorXi &labelIds = m_annotation.getLabelIds();
354 const FSLIB::Colortable &ct = m_annotation.getColortable();
355
356 for (int i = 0; i < labelIds.rows(); ++i) {
357 int vertexIdx = vertices(i);
358 if (vertexIdx >= 0 && vertexIdx < m_vertexData.size()) {
359 int colorIdx = -1;
360 for (int c = 0; c < ct.numEntries; ++c) {
361 if (ct.table(c, 4) == labelIds(i)) {
362 colorIdx = c;
363 break;
364 }
365 }
366 if (colorIdx >= 0) {
367 uint32_t r = ct.table(colorIdx, 0);
368 uint32_t g = ct.table(colorIdx, 1);
369 uint32_t b = ct.table(colorIdx, 2);
370 m_vertexData[vertexIdx].colorAnnotation =
371 packABGR(r, g, b);
372 }
373 }
374 }
375 }
376
377 // ── 3. Selection highlight (gold tint) — applied to BOTH channels
378 if (m_selected || m_selectedRegionId != -1) {
379 const uint32_t goldR = 255, goldG = 200, goldB = 80;
380 const float blendFactor = 0.4f;
381
382 auto blendToGold = [&](uint32_t &c) {
383 uint32_t a = (c >> 24) & 0xFF;
384 uint32_t bv = (c >> 16) & 0xFF;
385 uint32_t gv = (c >> 8) & 0xFF;
386 uint32_t rv = c & 0xFF;
387 rv = static_cast<uint32_t>(rv * (1.0f - blendFactor) + goldR * blendFactor);
388 gv = static_cast<uint32_t>(gv * (1.0f - blendFactor) + goldG * blendFactor);
389 bv = static_cast<uint32_t>(bv * (1.0f - blendFactor) + goldB * blendFactor);
390 c = packABGR(rv, gv, bv, a);
391 };
392
393 if (m_hasAnnotation && m_selectedRegionId != -1) {
394 const Eigen::VectorXi &vertices = m_annotation.getVertices();
395 const Eigen::VectorXi &labelIds = m_annotation.getLabelIds();
396 for (int i = 0; i < labelIds.rows(); ++i) {
397 if (labelIds(i) == m_selectedRegionId) {
398 int vertexIdx = vertices(i);
399 if (vertexIdx >= 0 && vertexIdx < m_vertexData.size()) {
400 blendToGold(m_vertexData[vertexIdx].color);
401 blendToGold(m_vertexData[vertexIdx].colorAnnotation);
402 }
403 }
404 }
405 } else if (m_selected) {
406 for (auto &v : m_vertexData) {
407 blendToGold(v.color);
408 blendToGold(v.colorAnnotation);
409 }
410 }
411 }
412
413 // ── 4. Vertex-range highlight (for batched sphere meshes)
414 if (m_selectedVertexStart >= 0 && m_selectedVertexCount > 0) {
415 const uint32_t hlR = 255, hlG = 255, hlB = 180;
416 const float blendFactor = 0.85f;
417 int end = qMin(m_selectedVertexStart + m_selectedVertexCount, (int)m_vertexData.size());
418 for (int i = m_selectedVertexStart; i < end; ++i) {
419 for (uint32_t *c : { &m_vertexData[i].color, &m_vertexData[i].colorAnnotation }) {
420 uint32_t a = (*c >> 24) & 0xFF;
421 uint32_t bv = (*c >> 16) & 0xFF;
422 uint32_t gv = (*c >> 8) & 0xFF;
423 uint32_t rv = *c & 0xFF;
424 rv = static_cast<uint32_t>(rv * (1.0f - blendFactor) + hlR * blendFactor);
425 gv = static_cast<uint32_t>(gv * (1.0f - blendFactor) + hlG * blendFactor);
426 bv = static_cast<uint32_t>(bv * (1.0f - blendFactor) + hlB * blendFactor);
427 *c = packABGR(rv, gv, bv, a);
428 }
429 }
430 }
431
432 m_gpu->dirty = true;
433}
434
436{
437 float minVal = std::numeric_limits<float>::max();
438 for (const auto &v : m_vertexData) {
439 if (v.pos.x() < minVal) minVal = v.pos.x();
440 }
441 return minVal;
442}
443
445{
446 float maxVal = std::numeric_limits<float>::lowest();
447 for (const auto &v : m_vertexData) {
448 if (v.pos.x() > maxVal) maxVal = v.pos.x();
449 }
450 return maxVal;
451}
452
453void BrainSurface::translateX(float offset)
454{
455 for (auto &v : m_vertexData) {
456 v.pos.setX(v.pos.x() + offset);
457 }
458 m_gpu->dirty = true;
459 m_bAABBDirty = true;
460}
461
462//=============================================================================================================
463
464void BrainSurface::transform(const QMatrix4x4 &m)
465{
466 // Extract 3x3 normal matrix (inverse transpose of upper-left 3x3)
467 // QMatrix4x4::normalMatrix() returns QMatrix3x3.
468 QMatrix3x3 normalMat = m.normalMatrix();
469
470 for (auto &v : m_vertexData) {
471 // Transform position
472 v.pos = m.map(v.pos);
473
474 // Transform normal
475 // Note: QMatrix3x3 * QVector3D isn't directly supported by some Qt versions conveniently,
476 // but let's assume standard multiplication works or do manually.
477 // Actually QVector3D operator*(QMatrix4x4) exists but is row-vector mul.
478 // QMatrix4x4 operator*(QVector3D) is standard column-vector mul.
479
480 // Use generic map method or just manual multiply if needed.
481 // Easier: mapVector for vectors (ignores translation) but needs to be normal matrix for non-uniform scales.
482 // If scale is uniform, mapVector is fine.
483
484 // Let's do it manually to be safe with QMatrix3x3
485 const float *d = normalMat.constData();
486 float nx = d[0]*v.norm.x() + d[3]*v.norm.y() + d[6]*v.norm.z();
487 float ny = d[1]*v.norm.x() + d[4]*v.norm.y() + d[7]*v.norm.z();
488 float nz = d[2]*v.norm.x() + d[5]*v.norm.y() + d[8]*v.norm.z();
489 v.norm = QVector3D(nx, ny, nz).normalized();
490 }
491 m_gpu->dirty = true;
492 m_bAABBDirty = true;
493}
494
495//=============================================================================================================
496
497void BrainSurface::applyTransform(const QMatrix4x4 &m)
498{
499 m_vertexData = m_originalVertexData;
500 if (!m.isIdentity()) {
501 transform(m);
502 } else {
503 m_gpu->dirty = true;
504 m_bAABBDirty = true;
505 }
506}
507
508//=============================================================================================================
509
510void BrainSurface::updateBuffers(QRhi *rhi, QRhiResourceUpdateBatch *u)
511{
512 if (!m_gpu->dirty && m_gpu->vertexBuffer && m_gpu->indexBuffer) return;
513
514 const quint32 vbufSize = m_vertexData.size() * sizeof(VertexData);
515 const quint32 ibufSize = m_indexData.size() * sizeof(uint32_t);
516
517 if (!m_gpu->vertexBuffer) {
518 m_gpu->vertexBuffer.reset(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, vbufSize));
519 m_gpu->vertexBuffer->create();
520 }
521 if (!m_gpu->indexBuffer) {
522 m_gpu->indexBuffer.reset(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::IndexBuffer, ibufSize));
523 m_gpu->indexBuffer->create();
524 }
525
526 u->uploadStaticBuffer(m_gpu->vertexBuffer.get(), m_vertexData.constData());
527 u->uploadStaticBuffer(m_gpu->indexBuffer.get(), m_indexData.constData());
528 m_gpu->dirty = false;
529}
530
531//=============================================================================================================
532
533std::vector<Eigen::VectorXi> BrainSurface::computeNeighbors() const
534{
535 // Use temporary std::vector<std::set<int>> for dedup during construction
536 std::vector<std::set<int>> tempNeighbors(m_vertexData.size());
537
538 // Triangles are stored in m_indexData as triplets
539 for (int i = 0; i + 2 < m_indexData.size(); i += 3) {
540 int v0 = m_indexData[i];
541 int v1 = m_indexData[i + 1];
542 int v2 = m_indexData[i + 2];
543
544 // Add bidirectional edges (set handles dedup)
545 tempNeighbors[v0].insert(v1);
546 tempNeighbors[v0].insert(v2);
547 tempNeighbors[v1].insert(v0);
548 tempNeighbors[v1].insert(v2);
549 tempNeighbors[v2].insert(v0);
550 tempNeighbors[v2].insert(v1);
551 }
552
553 // Convert to std::vector<VectorXi>
554 std::vector<Eigen::VectorXi> neighbors(tempNeighbors.size());
555 for (size_t k = 0; k < tempNeighbors.size(); ++k) {
556 const auto& s = tempNeighbors[k];
557 neighbors[k].resize(static_cast<Eigen::Index>(s.size()));
558 Eigen::Index idx = 0;
559 for (int val : s) {
560 neighbors[k][idx++] = val;
561 }
562 }
563
564 return neighbors;
565}
566
567//=============================================================================================================
568
569Eigen::MatrixX3f BrainSurface::verticesAsMatrix() const
570{
571 Eigen::MatrixX3f mat(m_vertexData.size(), 3);
572 for (int i = 0; i < m_vertexData.size(); ++i) {
573 mat(i, 0) = m_vertexData[i].pos.x();
574 mat(i, 1) = m_vertexData[i].pos.y();
575 mat(i, 2) = m_vertexData[i].pos.z();
576 }
577 return mat;
578}
579
580
581//=============================================================================================================
582
583void BrainSurface::boundingBox(QVector3D &min, QVector3D &max) const
584{
585 if (!m_bAABBDirty) {
586 min = m_aabbMin;
587 max = m_aabbMax;
588 return;
589 }
590
591 if (m_vertexData.isEmpty()) {
592 min = QVector3D(0,0,0);
593 max = QVector3D(0,0,0);
594 return;
595 }
596
597 min = m_vertexData[0].pos;
598 max = m_vertexData[0].pos;
599
600 for (const auto &v : m_vertexData) {
601 min.setX(std::min(min.x(), v.pos.x()));
602 min.setY(std::min(min.y(), v.pos.y()));
603 min.setZ(std::min(min.z(), v.pos.z()));
604
605 max.setX(std::max(max.x(), v.pos.x()));
606 max.setY(std::max(max.y(), v.pos.y()));
607 max.setZ(std::max(max.z(), v.pos.z()));
608 }
609
610 m_aabbMin = min;
611 m_aabbMax = max;
612 m_bAABBDirty = false;
613}
614
615bool BrainSurface::intersects(const QVector3D &rayOrigin, const QVector3D &rayDir, float &dist, int &vertexIdx) const
616{
617 vertexIdx = -1;
618 if (m_vertexData.isEmpty()) return false;
619
620 // 1. AABB Check (Cached)
621 QVector3D min, max;
622 boundingBox(min, max);
623
624 // Ray-AABB slab method (Double Precision for stability)
625 double eps = 1e-4;
626 double origin[3] = {rayOrigin.x(), rayOrigin.y(), rayOrigin.z()};
627 double dir[3] = {rayDir.x(), rayDir.y(), rayDir.z()};
628 double minB[3] = {min.x() - eps, min.y() - eps, min.z() - eps};
629 double maxB[3] = {max.x() + eps, max.y() + eps, max.z() + eps};
630
631 // Extract individual components for triangle intersection (Möller–Trumbore)
632 double originX = origin[0], originY = origin[1], originZ = origin[2];
633 double dirX = dir[0], dirY = dir[1], dirZ = dir[2];
634
635 double tmin = -std::numeric_limits<double>::max();
636 double tmax = std::numeric_limits<double>::max();
637
638 for (int i = 0; i < 3; ++i) {
639 if (std::abs(dir[i]) < 1e-15) {
640 if (origin[i] < minB[i] || origin[i] > maxB[i]) return false;
641 } else {
642 double t1 = (minB[i] - origin[i]) / dir[i];
643 double t2 = (maxB[i] - origin[i]) / dir[i];
644 if (t1 > t2) std::swap(t1, t2);
645 if (t1 > tmin) tmin = t1;
646 if (t2 < tmax) tmax = t2;
647 if (tmin > tmax) return false;
648 }
649 }
650
651 if (tmax < 1e-7) return false;
652
653 // 2. Triangle intersection
654 double closestDist = std::numeric_limits<double>::max();
655 bool hit = false;
656 int closestVert = -1;
657
658 // Brute-force triangle intersection using Double Precision Möller–Trumbore
659 // Note: For 100k+ vertices this is slow, ideally we'd use an Octree/BVH.
660 // However, since we only do this on mouse-over, results are usually acceptable if not too many surfaces are active.
661 for (int i = 0; i < m_indexData.size(); i += 3) {
662 int i0 = m_indexData[i];
663 int i1 = m_indexData[i+1];
664 int i2 = m_indexData[i+2];
665 const QVector3D &v0q = m_vertexData[i0].pos;
666 const QVector3D &v1q = m_vertexData[i1].pos;
667 const QVector3D &v2q = m_vertexData[i2].pos;
668
669 double v0x = v0q.x(), v0y = v0q.y(), v0z = v0q.z();
670 double v1x = v1q.x(), v1y = v1q.y(), v1z = v1q.z();
671 double v2x = v2q.x(), v2y = v2q.y(), v2z = v2q.z();
672
673 double edge1x = v1x - v0x, edge1y = v1y - v0y, edge1z = v1z - v0z;
674 double edge2x = v2x - v0x, edge2y = v2y - v0y, edge2z = v2z - v0z;
675
676 double hx = dirY * edge2z - dirZ * edge2y;
677 double hy = dirZ * edge2x - dirX * edge2z;
678 double hz = dirX * edge2y - dirY * edge2x;
679
680 double a = edge1x * hx + edge1y * hy + edge1z * hz;
681 if (std::abs(a) < 1e-18) continue; // Purely parallel
682
683 double f = 1.0 / a;
684 double sx = originX - v0x, sy = originY - v0y, sz = originZ - v0z;
685 double u = f * (sx * hx + sy * hy + sz * hz);
686 if (u < -1e-7 || u > 1.0000001) continue;
687
688 double qx = sy * edge1z - sz * edge1y;
689 double qy = sz * edge1x - sx * edge1z;
690 double qz = sx * edge1y - sy * edge1x;
691
692 double v = f * (dirX * qx + dirY * qy + dirZ * qz);
693 if (v < -1e-7 || u + v > 1.0000001) continue;
694
695 double t = f * (edge2x * qx + edge2y * qy + edge2z * qz);
696 if (t > 1e-7 && t < closestDist) {
697 // Check barycentric coordinates with Fixed Relative Tolerance (25%)
698 // Relative tolerance scales with triangle size:
699 // - Large triangles (Helmet): Tolerates large gaps (~cm scale)
700 // - Small triangles (Brain): Tolerates small errors (~mm scale)
701 // This prevents clicking 'through' sparse meshes while staying precise on dense ones.
702 constexpr double tol = 0.25;
703
704 if (u >= -tol && v >= -tol && u + v <= 1.0 + tol) {
705 closestDist = t;
706 hit = true;
707
708 // Find closest vertex of the hit triangle to the hit point
709 // (Used for region lookup)
710 double hitX = originX + t * dirX;
711 double hitY = originY + t * dirY;
712 double hitZ = originZ + t * dirZ;
713
714 double d0 = (v0x - hitX)*(v0x - hitX) + (v0y - hitY)*(v0y - hitY) + (v0z - hitZ)*(v0z - hitZ);
715 double d1 = (v1x - hitX)*(v1x - hitX) + (v1y - hitY)*(v1y - hitY) + (v1z - hitZ)*(v1z - hitZ);
716 double d2 = (v2x - hitX)*(v2x - hitX) + (v2y - hitY)*(v2y - hitY) + (v2z - hitZ)*(v2z - hitZ);
717
718 if (d0 < d1 && d0 < d2) closestVert = i0;
719 else if (d1 < d2) closestVert = i1;
720 else closestVert = i2;
721 }
722 }
723 }
724
725 if (hit) {
726 dist = static_cast<float>(closestDist);
727 vertexIdx = closestVert;
728 return true;
729 }
730
731 return false;
732}
733
734//=============================================================================================================
735
736QString BrainSurface::getAnnotationLabel(int vertexIdx) const
737{
738 if (!m_hasAnnotation || vertexIdx < 0 || vertexIdx >= m_vertexData.size()) {
739 return "";
740 }
741
742 const Eigen::VectorXi &vertices = m_annotation.getVertices();
743 const Eigen::VectorXi &labelIds = m_annotation.getLabelIds();
744 const FSLIB::Colortable &ct = m_annotation.getColortable();
745
746 // The .annot file might not contain all vertices if it's sparse,
747 // but usually it contains a mapping for all.
748 // Let's find the labelId for this vertex.
749 int labelId = -1;
750 for (int i = 0; i < vertices.rows(); ++i) {
751 if (vertices(i) == vertexIdx) {
752 labelId = labelIds(i);
753 break;
754 }
755 }
756
757 if (labelId == -1) return "Unknown";
758
759 // Find the name in colortable
760 for (int i = 0; i < ct.numEntries; ++i) {
761 if (ct.table(i, 4) == labelId) {
762 QString name = ct.struct_names[i];
763 // Remove null characters and trailing whitespace that might cause "strange signs"
764 while (!name.isEmpty() && (name.endsWith('\0') || name.endsWith(' '))) {
765 name.chop(1);
766 }
767 return name;
768 }
769 }
770
771 return "Unknown";
772}
773
775{
776 if (!m_hasAnnotation || vertexIdx < 0) return -1;
777
778 const Eigen::VectorXi &vertices = m_annotation.getVertices();
779 const Eigen::VectorXi &labelIds = m_annotation.getLabelIds();
780
781 for (int i = 0; i < vertices.rows(); ++i) {
782 if (vertices(i) == vertexIdx) {
783 return labelIds(i);
784 }
785 }
786
787 return -1;
788}
789
791{
792 if (m_selectedRegionId != regionId) {
793 m_selectedRegionId = regionId;
794 updateVertexColors();
795 m_gpu->dirty = true;
796 }
797}
798
799void BrainSurface::setSelected(bool selected)
800{
801 if (m_selected != selected) {
802 m_selected = selected;
803 updateVertexColors();
804 m_gpu->dirty = true;
805 }
806}
807
808void BrainSurface::setSelectedVertexRange(int start, int count)
809{
810 if (m_selectedVertexStart != start || m_selectedVertexCount != count) {
811 m_selectedVertexStart = start;
812 m_selectedVertexCount = count;
813 updateVertexColors();
814 m_gpu->dirty = true;
815 }
816}
BrainSurface class declaration.
uint32_t packABGR(uint32_t r, uint32_t g, uint32_t b, uint32_t a=0xFF)
Definition rendertypes.h:60
std::unique_ptr< QRhiBuffer > indexBuffer
std::unique_ptr< QRhiBuffer > vertexBuffer
Interleaved vertex attributes (position, normal, color, curvature) for brain surface GPU upload.
uint32_t colorAnnotation
QVector3D norm
QVector3D pos
uint32_t color
void translateX(float offset)
float minX() const
void setVisualizationMode(VisualizationMode mode)
Eigen::MatrixX3f vertexNormals() const
void setVisible(bool visible)
void boundingBox(QVector3D &min, QVector3D &max) const
QRhiBuffer * vertexBuffer() const
bool loadAnnotation(const QString &path)
void setSelectedRegion(int regionId)
void transform(const QMatrix4x4 &m)
::VisualizationMode VisualizationMode
void setSelectedVertexRange(int start, int count)
void fromSurface(const FSLIB::Surface &surf)
static constexpr VisualizationMode ModeSourceEstimate
QRhiBuffer * indexBuffer() const
bool intersects(const QVector3D &rayOrigin, const QVector3D &rayDir, float &dist, int &vertexIdx) const
void applySourceEstimateColors(const QVector< uint32_t > &colors)
void setUseDefaultColor(bool useDefault)
QString getAnnotationLabel(int vertexIdx) const
void updateBuffers(QRhi *rhi, QRhiResourceUpdateBatch *u)
float maxX() const
Eigen::MatrixX3f vertexPositions() const
int getAnnotationLabelId(int vertexIdx) const
void applyTransform(const QMatrix4x4 &m)
void setSelected(bool selected)
void addAnnotation(const FSLIB::Annotation &annotation)
Eigen::MatrixX3f verticesAsMatrix() const
void fromBemSurface(const MNELIB::MNEBemSurface &surf, const QColor &color=Qt::white)
void createFromData(const Eigen::MatrixX3f &vertices, const Eigen::MatrixX3i &triangles, const QColor &color)
std::vector< Eigen::VectorXi > computeNeighbors() const
Free surfer annotation.
Definition annotation.h:81
static bool read(const QString &subject_id, qint32 hemi, const QString &atlas, const QString &subjects_dir, Annotation &p_Annotation)
Vertices label based lookup table.
Definition colortable.h:68
Eigen::VectorXi getLabelIds() const
Definition colortable.h:120
QStringList struct_names
Definition colortable.h:112
Eigen::MatrixXi table
Definition colortable.h:113
FreeSurfer surface mesh.
Definition surface.h:76
const Eigen::MatrixX3f & nn() const
Definition surface.h:336
const Eigen::MatrixX3f & rr() const
Definition surface.h:322
const Eigen::VectorXf & curv() const
Definition surface.h:343
const Eigen::MatrixX3i & tris() const
Definition surface.h:329
static Eigen::MatrixX3f compute_normals(const Eigen::MatrixX3f &rr, const Eigen::MatrixX3i &tris)
Definition surface.cpp:133
BEM surface provides geometry information.