v2.0.0
Loading...
Searching...
No Matches
networkobject.cpp
Go to the documentation of this file.
1//=============================================================================================================
34
35//=============================================================================================================
36// INCLUDES
37//=============================================================================================================
38
39#include "networkobject.h"
40
41#include <rhi/qrhi.h>
42
46
47#include <QQuaternion>
48#include <QDebug>
49#include <cmath>
50
51using namespace CONNECTIVITYLIB;
52using namespace DISPLIB;
53using namespace Eigen;
54
55//=============================================================================================================
56// PIMPL
57//=============================================================================================================
58
60{
61 // Node buffers
62 std::unique_ptr<QRhiBuffer> nodeVertexBuffer;
63 std::unique_ptr<QRhiBuffer> nodeIndexBuffer;
64 std::unique_ptr<QRhiBuffer> nodeInstanceBuffer;
65 // Edge buffers
66 std::unique_ptr<QRhiBuffer> edgeVertexBuffer;
67 std::unique_ptr<QRhiBuffer> edgeIndexBuffer;
68 std::unique_ptr<QRhiBuffer> edgeInstanceBuffer;
69};
70
71//=============================================================================================================
72// DEFINE MEMBER METHODS
73//=============================================================================================================
74
76 : m_gpu(std::make_unique<GpuBuffers>())
77{
78}
79
80//=============================================================================================================
81
83
84//=============================================================================================================
85
86QRhiBuffer* NetworkObject::nodeVertexBuffer() const { return m_gpu->nodeVertexBuffer.get(); }
87QRhiBuffer* NetworkObject::nodeIndexBuffer() const { return m_gpu->nodeIndexBuffer.get(); }
88QRhiBuffer* NetworkObject::nodeInstanceBuffer() const { return m_gpu->nodeInstanceBuffer.get(); }
89QRhiBuffer* NetworkObject::edgeVertexBuffer() const { return m_gpu->edgeVertexBuffer.get(); }
90QRhiBuffer* NetworkObject::edgeIndexBuffer() const { return m_gpu->edgeIndexBuffer.get(); }
91QRhiBuffer* NetworkObject::edgeInstanceBuffer() const { return m_gpu->edgeInstanceBuffer.get(); }
92
93//=============================================================================================================
94
95void NetworkObject::load(const Network &network, const QString &sColormap)
96{
97 m_network = network;
98 m_colormap = sColormap;
99
100 createNodeGeometry();
101 createEdgeGeometry();
102 buildNodeInstances();
103 buildEdgeInstances();
104}
105
106//=============================================================================================================
107
108void NetworkObject::setThreshold(double dThreshold)
109{
110 m_network.setThreshold(dThreshold);
111 buildNodeInstances();
112 buildEdgeInstances();
113}
114
115//=============================================================================================================
116
117void NetworkObject::setColormap(const QString &sColormap)
118{
119 m_colormap = sColormap;
120 buildNodeInstances();
121 buildEdgeInstances();
122}
123
124//=============================================================================================================
125
126void NetworkObject::createNodeGeometry()
127{
128 if (!m_nodeVertexData.isEmpty()) return;
129
130 // Create an icosphere (subdivision level 1) for nodes
131 const int subdivisions = 1;
132 const float radius = 1.0f; // Unit sphere, scaled per-instance
133
134 // Start with icosahedron
135 const float t = (1.0f + std::sqrt(5.0f)) / 2.0f;
136
137 std::vector<QVector3D> vertices = {
138 QVector3D(-1, t, 0).normalized() * radius,
139 QVector3D( 1, t, 0).normalized() * radius,
140 QVector3D(-1, -t, 0).normalized() * radius,
141 QVector3D( 1, -t, 0).normalized() * radius,
142 QVector3D( 0, -1, t).normalized() * radius,
143 QVector3D( 0, 1, t).normalized() * radius,
144 QVector3D( 0, -1, -t).normalized() * radius,
145 QVector3D( 0, 1, -t).normalized() * radius,
146 QVector3D( t, 0, -1).normalized() * radius,
147 QVector3D( t, 0, 1).normalized() * radius,
148 QVector3D(-t, 0, -1).normalized() * radius,
149 QVector3D(-t, 0, 1).normalized() * radius,
150 };
151
152 std::vector<uint32_t> indices = {
153 0,11,5, 0,5,1, 0,1,7, 0,7,10, 0,10,11,
154 1,5,9, 5,11,4, 11,10,2, 10,7,6, 7,1,8,
155 3,9,4, 3,4,2, 3,2,6, 3,6,8, 3,8,9,
156 4,9,5, 2,4,11, 6,2,10, 8,6,7, 9,8,1,
157 };
158
159 // Subdivide
160 for (int s = 0; s < subdivisions; ++s) {
161 std::vector<uint32_t> newIndices;
162 std::map<uint64_t, uint32_t> midpointCache;
163
164 auto getMidpoint = [&](uint32_t i0, uint32_t i1) -> uint32_t {
165 uint64_t key = (uint64_t)std::min(i0, i1) << 32 | std::max(i0, i1);
166 auto it = midpointCache.find(key);
167 if (it != midpointCache.end()) return it->second;
168
169 QVector3D mid = ((vertices[i0] + vertices[i1]) / 2.0f).normalized() * radius;
170 uint32_t idx = (uint32_t)vertices.size();
171 vertices.push_back(mid);
172 midpointCache[key] = idx;
173 return idx;
174 };
175
176 for (size_t i = 0; i < indices.size(); i += 3) {
177 uint32_t a = indices[i], b = indices[i + 1], c = indices[i + 2];
178 uint32_t ab = getMidpoint(a, b);
179 uint32_t bc = getMidpoint(b, c);
180 uint32_t ca = getMidpoint(c, a);
181
182 newIndices.insert(newIndices.end(), {a, ab, ca});
183 newIndices.insert(newIndices.end(), {b, bc, ab});
184 newIndices.insert(newIndices.end(), {c, ca, bc});
185 newIndices.insert(newIndices.end(), {ab, bc, ca});
186 }
187
188 indices = std::move(newIndices);
189 }
190
191 // Build vertex data with normals (normal = normalized position for sphere)
192 std::vector<VertexData> vd;
193 vd.reserve(vertices.size());
194 for (const auto &v : vertices) {
195 QVector3D n = v.normalized();
196 vd.push_back({v.x(), v.y(), v.z(), n.x(), n.y(), n.z()});
197 }
198
199 m_nodeIndexCount = (int)indices.size();
200
201 m_nodeVertexData.resize(vd.size() * sizeof(VertexData));
202 memcpy(m_nodeVertexData.data(), vd.data(), m_nodeVertexData.size());
203
204 m_nodeIndexData.resize(indices.size() * sizeof(uint32_t));
205 memcpy(m_nodeIndexData.data(), indices.data(), m_nodeIndexData.size());
206
207 m_nodeGeometryDirty = true;
208}
209
210//=============================================================================================================
211
212void NetworkObject::createEdgeGeometry()
213{
214 if (!m_edgeVertexData.isEmpty()) return;
215
216 // Create a unit cylinder along Y axis (height=1, radius=1, scaled per-instance)
217 const int segments = 8;
218 const float radius = 1.0f;
219 const float halfHeight = 0.5f;
220
221 std::vector<VertexData> vertices;
222 std::vector<uint32_t> indices;
223
224 // Top center (0)
225 vertices.push_back({0, halfHeight, 0, 0, 1, 0});
226 // Bottom center (1)
227 vertices.push_back({0, -halfHeight, 0, 0, -1, 0});
228
229 // Side vertices: top ring (2..2+segments-1), bottom ring (2+segments..2+2*segments-1)
230 for (int i = 0; i < segments; ++i) {
231 float angle = 2.0f * (float)M_PI * i / segments;
232 float x = radius * std::cos(angle);
233 float z = radius * std::sin(angle);
234
235 QVector3D normal(x, 0, z);
236 normal.normalize();
237
238 // Top side vertex
239 vertices.push_back({x, halfHeight, z, normal.x(), normal.y(), normal.z()});
240 // Bottom side vertex
241 vertices.push_back({x, -halfHeight, z, normal.x(), normal.y(), normal.z()});
242 }
243
244 // Top cap vertices (for proper normals)
245 int topCapStart = (int)vertices.size();
246 for (int i = 0; i < segments; ++i) {
247 float angle = 2.0f * (float)M_PI * i / segments;
248 float x = radius * std::cos(angle);
249 float z = radius * std::sin(angle);
250 vertices.push_back({x, halfHeight, z, 0, 1, 0});
251 }
252
253 // Bottom cap vertices
254 int botCapStart = (int)vertices.size();
255 for (int i = 0; i < segments; ++i) {
256 float angle = 2.0f * (float)M_PI * i / segments;
257 float x = radius * std::cos(angle);
258 float z = radius * std::sin(angle);
259 vertices.push_back({x, -halfHeight, z, 0, -1, 0});
260 }
261
262 // Side faces
263 int sideStart = 2;
264 for (int i = 0; i < segments; ++i) {
265 int next = (i + 1) % segments;
266 int topCur = sideStart + i * 2;
267 int botCur = sideStart + i * 2 + 1;
268 int topNext = sideStart + next * 2;
269 int botNext = sideStart + next * 2 + 1;
270
271 indices.insert(indices.end(), {(uint32_t)topCur, (uint32_t)topNext, (uint32_t)botCur});
272 indices.insert(indices.end(), {(uint32_t)botCur, (uint32_t)topNext, (uint32_t)botNext});
273 }
274
275 // Top cap
276 for (int i = 0; i < segments; ++i) {
277 int next = (i + 1) % segments;
278 indices.push_back(0); // center
279 indices.push_back(topCapStart + i);
280 indices.push_back(topCapStart + next);
281 }
282
283 // Bottom cap
284 for (int i = 0; i < segments; ++i) {
285 int next = (i + 1) % segments;
286 indices.push_back(1); // center
287 indices.push_back(botCapStart + next);
288 indices.push_back(botCapStart + i);
289 }
290
291 m_edgeIndexCount = (int)indices.size();
292
293 m_edgeVertexData.resize(vertices.size() * sizeof(VertexData));
294 memcpy(m_edgeVertexData.data(), vertices.data(), m_edgeVertexData.size());
295
296 m_edgeIndexData.resize(indices.size() * sizeof(uint32_t));
297 memcpy(m_edgeIndexData.data(), indices.data(), m_edgeIndexData.size());
298
299 m_edgeGeometryDirty = true;
300}
301
302//=============================================================================================================
303
304void NetworkObject::buildNodeInstances()
305{
306 if (m_network.isEmpty()) {
307 m_nodeInstanceCount = 0;
308 m_nodeInstancesDirty = true;
309 return;
310 }
311
312 const auto &nodes = m_network.getNodes();
313 qint16 iMaxDegree = m_network.getMinMaxThresholdedDegrees().second;
314 if (iMaxDegree == 0) iMaxDegree = 1;
315
316 VisualizationInfo vizInfo = m_network.getVisualizationInfo();
317
318 std::vector<InstanceData> instances;
319 instances.reserve(nodes.size());
320
321 for (int i = 0; i < nodes.size(); ++i) {
322 qint16 degree = nodes[i]->getThresholdedDegree();
323 if (degree == 0) continue;
324
325 const RowVectorXf &vert = nodes[i]->getVert();
326 QVector3D pos(vert(0), vert(1), vert(2));
327
328 // Scale: nodes with higher degree are larger
329 // Range: 0.0006 to 0.005 (same as disp3D)
330 float scaleFactor = ((float)degree / (float)iMaxDegree) * (0.005f - 0.0006f) + 0.0006f;
331
332 QMatrix4x4 m;
333 m.translate(pos);
334 m.scale(scaleFactor);
335
336 InstanceData inst;
337 const float *mPtr = m.constData();
338 for (int j = 0; j < 16; ++j) inst.model[j] = mPtr[j];
339
340 // Color: colormap-based or fixed
341 if (vizInfo.sMethod == "Map") {
342 float normalized = (float)degree / (float)iMaxDegree;
343 QRgb rgb = ColorMap::valueToColor(normalized, vizInfo.sColormap.isEmpty() ? m_colormap : vizInfo.sColormap);
344 QColor color(rgb);
345 float alpha = std::pow(normalized, 4.0f); // Same as disp3D
346 inst.color[0] = color.redF();
347 inst.color[1] = color.greenF();
348 inst.color[2] = color.blueF();
349 inst.color[3] = alpha;
350 } else {
351 inst.color[0] = vizInfo.colNodes[0] / 255.0f;
352 inst.color[1] = vizInfo.colNodes[1] / 255.0f;
353 inst.color[2] = vizInfo.colNodes[2] / 255.0f;
354 inst.color[3] = vizInfo.colNodes[3] / 255.0f;
355 }
356 inst.isSelected = 0.0f;
357
358 instances.push_back(inst);
359 }
360
361 m_nodeInstanceCount = (int)instances.size();
362 m_nodeInstanceData.resize(m_nodeInstanceCount * sizeof(InstanceData));
363 if (m_nodeInstanceCount > 0) {
364 memcpy(m_nodeInstanceData.data(), instances.data(), m_nodeInstanceData.size());
365 }
366 m_nodeInstancesDirty = true;
367
368 qDebug() << "NetworkObject: Built" << m_nodeInstanceCount << "node instances";
369}
370
371//=============================================================================================================
372
373void NetworkObject::buildEdgeInstances()
374{
375 if (m_network.isEmpty()) {
376 m_edgeInstanceCount = 0;
377 m_edgeInstancesDirty = true;
378 return;
379 }
380
381 const auto &edges = m_network.getThresholdedEdges();
382 const auto &nodes = m_network.getNodes();
383
384 double dMaxWeight = m_network.getMinMaxThresholdedWeights().second;
385 double dMinWeight = m_network.getMinMaxThresholdedWeights().first;
386 double dWeightRange = dMaxWeight - dMinWeight;
387 if (dWeightRange == 0.0) dWeightRange = 1.0;
388
389 VisualizationInfo vizInfo = m_network.getVisualizationInfo();
390
391 std::vector<InstanceData> instances;
392 instances.reserve(edges.size());
393
394 for (int i = 0; i < edges.size(); ++i) {
395 auto &edge = edges[i];
396 if (!edge->isActive()) continue;
397
398 int iStart = edge->getStartNodeID();
399 int iEnd = edge->getEndNodeID();
400
401 if (iStart < 0 || iStart >= nodes.size() || iEnd < 0 || iEnd >= nodes.size()) continue;
402
403 const RowVectorXf &vStart = nodes[iStart]->getVert();
404 const RowVectorXf &vEnd = nodes[iEnd]->getVert();
405
406 QVector3D startPos(vStart(0), vStart(1), vStart(2));
407 QVector3D endPos(vEnd(0), vEnd(1), vEnd(2));
408
409 if (startPos == endPos) continue;
410
411 double dWeight = std::fabs(edge->getWeight());
412 if (dWeight == 0.0) continue;
413
414 QVector3D diff = endPos - startPos;
415 QVector3D midPoint = startPos + diff / 2.0f;
416 float length = diff.length();
417
418 // Build transform: translate to midpoint, rotate Y-axis to diff direction, scale
419 float normalizedWeight = (float)std::fabs((dWeight - dMinWeight) / dWeightRange);
420
421 // Cylinder radius: proportional to weight, range 0.0001 to 0.001
422 float edgeRadius = 0.0001f + normalizedWeight * 0.0009f;
423
424 QMatrix4x4 m;
425 m.translate(midPoint);
426 m.rotate(QQuaternion::rotationTo(QVector3D(0, 1, 0), diff.normalized()));
427 m.scale(edgeRadius, length, edgeRadius);
428
429 InstanceData inst;
430 const float *mPtr = m.constData();
431 for (int j = 0; j < 16; ++j) inst.model[j] = mPtr[j];
432
433 // Color
434 if (vizInfo.sMethod == "Map") {
435 float normalized = (dMaxWeight != 0.0) ? (float)std::fabs(dWeight / dMaxWeight) : 0.0f;
436 QRgb rgb = ColorMap::valueToColor(normalized, vizInfo.sColormap.isEmpty() ? m_colormap : vizInfo.sColormap);
437 QColor color(rgb);
438 float alpha = std::pow(normalized, 1.5f); // Same as disp3D
439 inst.color[0] = color.redF();
440 inst.color[1] = color.greenF();
441 inst.color[2] = color.blueF();
442 inst.color[3] = alpha;
443 } else {
444 inst.color[0] = vizInfo.colEdges[0] / 255.0f;
445 inst.color[1] = vizInfo.colEdges[1] / 255.0f;
446 inst.color[2] = vizInfo.colEdges[2] / 255.0f;
447 inst.color[3] = vizInfo.colEdges[3] / 255.0f;
448 }
449 inst.isSelected = 0.0f;
450
451 instances.push_back(inst);
452 }
453
454 m_edgeInstanceCount = (int)instances.size();
455 m_edgeInstanceData.resize(m_edgeInstanceCount * sizeof(InstanceData));
456 if (m_edgeInstanceCount > 0) {
457 memcpy(m_edgeInstanceData.data(), instances.data(), m_edgeInstanceData.size());
458 }
459 m_edgeInstancesDirty = true;
460
461 qDebug() << "NetworkObject: Built" << m_edgeInstanceCount << "edge instances";
462}
463
464//=============================================================================================================
465
466void NetworkObject::updateNodeBuffers(QRhi *rhi, QRhiResourceUpdateBatch *u)
467{
468 if (m_nodeGeometryDirty) {
469 if (!m_gpu->nodeVertexBuffer) {
470 m_gpu->nodeVertexBuffer.reset(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, m_nodeVertexData.size()));
471 m_gpu->nodeVertexBuffer->create();
472 }
473 if (!m_gpu->nodeIndexBuffer) {
474 m_gpu->nodeIndexBuffer.reset(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::IndexBuffer, m_nodeIndexData.size()));
475 m_gpu->nodeIndexBuffer->create();
476 }
477 u->uploadStaticBuffer(m_gpu->nodeVertexBuffer.get(), m_nodeVertexData.constData());
478 u->uploadStaticBuffer(m_gpu->nodeIndexBuffer.get(), m_nodeIndexData.constData());
479 m_nodeGeometryDirty = false;
480 }
481
482 if (m_nodeInstancesDirty && m_nodeInstanceCount > 0) {
483 int requiredSize = m_nodeInstanceData.size();
484 if (!m_gpu->nodeInstanceBuffer || m_gpu->nodeInstanceBuffer->size() < requiredSize) {
485 m_gpu->nodeInstanceBuffer.reset(rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::VertexBuffer, requiredSize));
486 m_gpu->nodeInstanceBuffer->create();
487 }
488 u->updateDynamicBuffer(m_gpu->nodeInstanceBuffer.get(), 0, requiredSize, m_nodeInstanceData.constData());
489 m_nodeInstancesDirty = false;
490 }
491}
492
493//=============================================================================================================
494
495void NetworkObject::updateEdgeBuffers(QRhi *rhi, QRhiResourceUpdateBatch *u)
496{
497 if (m_edgeGeometryDirty) {
498 if (!m_gpu->edgeVertexBuffer) {
499 m_gpu->edgeVertexBuffer.reset(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, m_edgeVertexData.size()));
500 m_gpu->edgeVertexBuffer->create();
501 }
502 if (!m_gpu->edgeIndexBuffer) {
503 m_gpu->edgeIndexBuffer.reset(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::IndexBuffer, m_edgeIndexData.size()));
504 m_gpu->edgeIndexBuffer->create();
505 }
506 u->uploadStaticBuffer(m_gpu->edgeVertexBuffer.get(), m_edgeVertexData.constData());
507 u->uploadStaticBuffer(m_gpu->edgeIndexBuffer.get(), m_edgeIndexData.constData());
508 m_edgeGeometryDirty = false;
509 }
510
511 if (m_edgeInstancesDirty && m_edgeInstanceCount > 0) {
512 int requiredSize = m_edgeInstanceData.size();
513 if (!m_gpu->edgeInstanceBuffer || m_gpu->edgeInstanceBuffer->size() < requiredSize) {
514 m_gpu->edgeInstanceBuffer.reset(rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::VertexBuffer, requiredSize));
515 m_gpu->edgeInstanceBuffer->create();
516 }
517 u->updateDynamicBuffer(m_gpu->edgeInstanceBuffer.get(), 0, requiredSize, m_edgeInstanceData.constData());
518 m_edgeInstancesDirty = false;
519 }
520}
#define M_PI
ColorMap class declaration.
NetworkObject class declaration.
NetworkEdge class declaration.
NetworkNode class declaration.
Functional connectivity metrics (coherence, PLV, cross-correlation, etc.).
2-D display widgets and visualisation helpers (charts, topography, colour maps).
This class holds information about a network, can compute a distance table and provide network metric...
Definition network.h:92
static QRgb valueToColor(double v, const QString &sMap)
Definition colormap.h:688
std::unique_ptr< QRhiBuffer > nodeVertexBuffer
std::unique_ptr< QRhiBuffer > edgeInstanceBuffer
std::unique_ptr< QRhiBuffer > nodeInstanceBuffer
std::unique_ptr< QRhiBuffer > nodeIndexBuffer
std::unique_ptr< QRhiBuffer > edgeVertexBuffer
std::unique_ptr< QRhiBuffer > edgeIndexBuffer
QRhiBuffer * nodeIndexBuffer() const
void setColormap(const QString &sColormap)
void updateNodeBuffers(QRhi *rhi, QRhiResourceUpdateBatch *u)
QRhiBuffer * edgeIndexBuffer() const
void setThreshold(double dThreshold)
void load(const CONNECTIVITYLIB::Network &network, const QString &sColormap="Viridis")
QRhiBuffer * nodeInstanceBuffer() const
QRhiBuffer * edgeVertexBuffer() const
QRhiBuffer * nodeVertexBuffer() const
void updateEdgeBuffers(QRhi *rhi, QRhiResourceUpdateBatch *u)
QRhiBuffer * edgeInstanceBuffer() const