69 return m_gpu->nodeVertexBuffer.get();
73 return m_gpu->nodeIndexBuffer.get();
77 return m_gpu->nodeInstanceBuffer.get();
81 return m_gpu->edgeVertexBuffer.get();
85 return m_gpu->edgeIndexBuffer.get();
89 return m_gpu->edgeInstanceBuffer.get();
97 m_colormap = sColormap;
100 createEdgeGeometry();
101 buildNodeInstances();
102 buildEdgeInstances();
109 m_network.setThreshold(dThreshold);
110 buildNodeInstances();
111 buildEdgeInstances();
118 m_colormap = sColormap;
119 buildNodeInstances();
120 buildEdgeInstances();
125void NetworkObject::createNodeGeometry()
127 if (!m_nodeVertexData.isEmpty())
131 const int subdivisions = 1;
132 const float radius = 1.0f;
135 const float t = (1.0f + std::sqrt(5.0f)) / 2.0f;
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,
152 std::vector<uint32_t> indices = {
216 for (
int s = 0; s < subdivisions; ++s) {
217 std::vector<uint32_t> newIndices;
218 std::map<uint64_t, uint32_t> midpointCache;
220 auto getMidpoint = [&](uint32_t i0, uint32_t i1) -> uint32_t {
221 uint64_t key = (uint64_t)std::min(i0, i1) << 32 | std::max(i0, i1);
222 auto it = midpointCache.find(key);
223 if (it != midpointCache.end())
226 QVector3D mid = ((vertices[i0] + vertices[i1]) / 2.0f).normalized() * radius;
227 uint32_t idx = (uint32_t)vertices.size();
228 vertices.push_back(mid);
229 midpointCache[key] = idx;
233 for (
size_t i = 0; i < indices.size(); i += 3) {
234 uint32_t a = indices[i], b = indices[i + 1], c = indices[i + 2];
235 uint32_t ab = getMidpoint(a, b);
236 uint32_t bc = getMidpoint(b, c);
237 uint32_t ca = getMidpoint(c, a);
239 newIndices.insert(newIndices.end(), {a, ab, ca});
240 newIndices.insert(newIndices.end(), {b, bc, ab});
241 newIndices.insert(newIndices.end(), {c, ca, bc});
242 newIndices.insert(newIndices.end(), {ab, bc, ca});
245 indices = std::move(newIndices);
249 std::vector<VertexData> vd;
250 vd.reserve(vertices.size());
251 for (
const auto& v : vertices) {
252 QVector3D n = v.normalized();
253 vd.push_back({v.x(), v.y(), v.z(), n.x(), n.y(), n.z()});
256 m_nodeIndexCount = (int)indices.size();
258 m_nodeVertexData.resize(vd.size() *
sizeof(VertexData));
259 memcpy(m_nodeVertexData.data(), vd.data(), m_nodeVertexData.size());
261 m_nodeIndexData.resize(indices.size() *
sizeof(uint32_t));
262 memcpy(m_nodeIndexData.data(), indices.data(), m_nodeIndexData.size());
264 m_nodeGeometryDirty =
true;
269void NetworkObject::createEdgeGeometry()
271 if (!m_edgeVertexData.isEmpty())
275 const int segments = 8;
276 const float radius = 1.0f;
277 const float halfHeight = 0.5f;
279 std::vector<VertexData> vertices;
280 std::vector<uint32_t> indices;
283 vertices.push_back({0, halfHeight, 0, 0, 1, 0});
285 vertices.push_back({0, -halfHeight, 0, 0, -1, 0});
288 for (
int i = 0; i < segments; ++i) {
289 float angle = 2.0f * (float)
M_PI * i / segments;
290 float x = radius * std::cos(angle);
291 float z = radius * std::sin(angle);
293 QVector3D normal(x, 0, z);
297 vertices.push_back({x, halfHeight, z, normal.x(), normal.y(), normal.z()});
299 vertices.push_back({x, -halfHeight, z, normal.x(), normal.y(), normal.z()});
303 int topCapStart = (int)vertices.size();
304 for (
int i = 0; i < segments; ++i) {
305 float angle = 2.0f * (float)
M_PI * i / segments;
306 float x = radius * std::cos(angle);
307 float z = radius * std::sin(angle);
308 vertices.push_back({x, halfHeight, z, 0, 1, 0});
312 int botCapStart = (int)vertices.size();
313 for (
int i = 0; i < segments; ++i) {
314 float angle = 2.0f * (float)
M_PI * i / segments;
315 float x = radius * std::cos(angle);
316 float z = radius * std::sin(angle);
317 vertices.push_back({x, -halfHeight, z, 0, -1, 0});
322 for (
int i = 0; i < segments; ++i) {
323 int next = (i + 1) % segments;
324 int topCur = sideStart + i * 2;
325 int botCur = sideStart + i * 2 + 1;
326 int topNext = sideStart + next * 2;
327 int botNext = sideStart + next * 2 + 1;
329 indices.insert(indices.end(), {(uint32_t)topCur, (uint32_t)topNext, (uint32_t)botCur});
330 indices.insert(indices.end(), {(uint32_t)botCur, (uint32_t)topNext, (uint32_t)botNext});
334 for (
int i = 0; i < segments; ++i) {
335 int next = (i + 1) % segments;
336 indices.push_back(0);
337 indices.push_back(topCapStart + i);
338 indices.push_back(topCapStart + next);
342 for (
int i = 0; i < segments; ++i) {
343 int next = (i + 1) % segments;
344 indices.push_back(1);
345 indices.push_back(botCapStart + next);
346 indices.push_back(botCapStart + i);
349 m_edgeIndexCount = (int)indices.size();
351 m_edgeVertexData.resize(vertices.size() *
sizeof(VertexData));
352 memcpy(m_edgeVertexData.data(), vertices.data(), m_edgeVertexData.size());
354 m_edgeIndexData.resize(indices.size() *
sizeof(uint32_t));
355 memcpy(m_edgeIndexData.data(), indices.data(), m_edgeIndexData.size());
357 m_edgeGeometryDirty =
true;
362void NetworkObject::buildNodeInstances()
364 if (m_network.isEmpty()) {
365 m_nodeInstanceCount = 0;
366 m_nodeInstancesDirty =
true;
370 const auto& nodes = m_network.getNodes();
371 qint16 iMaxDegree = m_network.getMinMaxThresholdedDegrees().second;
375 VisualizationInfo vizInfo = m_network.getVisualizationInfo();
377 std::vector<InstanceData> instances;
378 instances.reserve(nodes.size());
380 for (
int i = 0; i < nodes.size(); ++i) {
381 qint16 degree = nodes[i]->getThresholdedDegree();
385 const RowVectorXf& vert = nodes[i]->getVert();
386 QVector3D pos(vert(0), vert(1), vert(2));
390 float scaleFactor = ((float)degree / (float)iMaxDegree) * (0.005f - 0.0006f) + 0.0006f;
394 m.scale(scaleFactor);
397 const float* mPtr = m.constData();
398 for (
int j = 0; j < 16; ++j)
399 inst.model[j] = mPtr[j];
402 if (vizInfo.
sMethod ==
"Map") {
403 float normalized = (float)degree / (
float)iMaxDegree;
406 float alpha = std::pow(normalized, 4.0f);
407 inst.color[0] = color.redF();
408 inst.color[1] = color.greenF();
409 inst.color[2] = color.blueF();
410 inst.color[3] = alpha;
412 inst.color[0] = vizInfo.
colNodes[0] / 255.0f;
413 inst.color[1] = vizInfo.
colNodes[1] / 255.0f;
414 inst.color[2] = vizInfo.
colNodes[2] / 255.0f;
415 inst.color[3] = vizInfo.
colNodes[3] / 255.0f;
417 inst.isSelected = 0.0f;
419 instances.push_back(inst);
422 m_nodeInstanceCount = (int)instances.size();
423 m_nodeInstanceData.resize(m_nodeInstanceCount *
sizeof(InstanceData));
424 if (m_nodeInstanceCount > 0) {
425 memcpy(m_nodeInstanceData.data(), instances.data(), m_nodeInstanceData.size());
427 m_nodeInstancesDirty =
true;
429 qDebug() <<
"NetworkObject: Built" << m_nodeInstanceCount <<
"node instances";
434void NetworkObject::buildEdgeInstances()
436 if (m_network.isEmpty()) {
437 m_edgeInstanceCount = 0;
438 m_edgeInstancesDirty =
true;
442 const auto& edges = m_network.getThresholdedEdges();
443 const auto& nodes = m_network.getNodes();
445 double dMaxWeight = m_network.getMinMaxThresholdedWeights().second;
446 double dMinWeight = m_network.getMinMaxThresholdedWeights().first;
447 double dWeightRange = dMaxWeight - dMinWeight;
448 if (dWeightRange == 0.0)
451 VisualizationInfo vizInfo = m_network.getVisualizationInfo();
453 std::vector<InstanceData> instances;
454 instances.reserve(edges.size());
456 for (
int i = 0; i < edges.size(); ++i) {
457 auto& edge = edges[i];
458 if (!edge->isActive())
461 int iStart = edge->getStartNodeID();
462 int iEnd = edge->getEndNodeID();
464 if (iStart < 0 || iStart >= nodes.size() || iEnd < 0 || iEnd >= nodes.size())
467 const RowVectorXf& vStart = nodes[iStart]->getVert();
468 const RowVectorXf& vEnd = nodes[iEnd]->getVert();
470 QVector3D startPos(vStart(0), vStart(1), vStart(2));
471 QVector3D endPos(vEnd(0), vEnd(1), vEnd(2));
473 if (startPos == endPos)
476 double dWeight = std::fabs(edge->getWeight());
480 QVector3D diff = endPos - startPos;
481 QVector3D midPoint = startPos + diff / 2.0f;
482 float length = diff.length();
485 float normalizedWeight = (float)std::fabs((dWeight - dMinWeight) / dWeightRange);
488 float edgeRadius = 0.0001f + normalizedWeight * 0.0009f;
491 m.translate(midPoint);
492 m.rotate(QQuaternion::rotationTo(QVector3D(0, 1, 0), diff.normalized()));
493 m.scale(edgeRadius, length, edgeRadius);
496 const float* mPtr = m.constData();
497 for (
int j = 0; j < 16; ++j)
498 inst.model[j] = mPtr[j];
501 if (vizInfo.
sMethod ==
"Map") {
502 float normalized = (dMaxWeight != 0.0) ? (
float)std::fabs(dWeight / dMaxWeight) : 0.0f;
505 float alpha = std::pow(normalized, 1.5f);
506 inst.color[0] = color.redF();
507 inst.color[1] = color.greenF();
508 inst.color[2] = color.blueF();
509 inst.color[3] = alpha;
511 inst.color[0] = vizInfo.
colEdges[0] / 255.0f;
512 inst.color[1] = vizInfo.
colEdges[1] / 255.0f;
513 inst.color[2] = vizInfo.
colEdges[2] / 255.0f;
514 inst.color[3] = vizInfo.
colEdges[3] / 255.0f;
516 inst.isSelected = 0.0f;
518 instances.push_back(inst);
521 m_edgeInstanceCount = (int)instances.size();
522 m_edgeInstanceData.resize(m_edgeInstanceCount *
sizeof(InstanceData));
523 if (m_edgeInstanceCount > 0) {
524 memcpy(m_edgeInstanceData.data(), instances.data(), m_edgeInstanceData.size());
526 m_edgeInstancesDirty =
true;
528 qDebug() <<
"NetworkObject: Built" << m_edgeInstanceCount <<
"edge instances";
535 if (m_nodeGeometryDirty) {
536 if (!m_gpu->nodeVertexBuffer) {
537 m_gpu->nodeVertexBuffer.reset(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, m_nodeVertexData.size()));
538 m_gpu->nodeVertexBuffer->create();
540 if (!m_gpu->nodeIndexBuffer) {
541 m_gpu->nodeIndexBuffer.reset(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::IndexBuffer, m_nodeIndexData.size()));
542 m_gpu->nodeIndexBuffer->create();
544 u->uploadStaticBuffer(m_gpu->nodeVertexBuffer.get(), m_nodeVertexData.constData());
545 u->uploadStaticBuffer(m_gpu->nodeIndexBuffer.get(), m_nodeIndexData.constData());
546 m_nodeGeometryDirty =
false;
549 if (m_nodeInstancesDirty && m_nodeInstanceCount > 0) {
550 int requiredSize = m_nodeInstanceData.size();
551 if (!m_gpu->nodeInstanceBuffer || m_gpu->nodeInstanceBuffer->size() <
static_cast<quint32
>(requiredSize)) {
552 m_gpu->nodeInstanceBuffer.reset(rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::VertexBuffer, requiredSize));
553 m_gpu->nodeInstanceBuffer->create();
555 u->updateDynamicBuffer(m_gpu->nodeInstanceBuffer.get(), 0, requiredSize, m_nodeInstanceData.constData());
556 m_nodeInstancesDirty =
false;
564 if (m_edgeGeometryDirty) {
565 if (!m_gpu->edgeVertexBuffer) {
566 m_gpu->edgeVertexBuffer.reset(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, m_edgeVertexData.size()));
567 m_gpu->edgeVertexBuffer->create();
569 if (!m_gpu->edgeIndexBuffer) {
570 m_gpu->edgeIndexBuffer.reset(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::IndexBuffer, m_edgeIndexData.size()));
571 m_gpu->edgeIndexBuffer->create();
573 u->uploadStaticBuffer(m_gpu->edgeVertexBuffer.get(), m_edgeVertexData.constData());
574 u->uploadStaticBuffer(m_gpu->edgeIndexBuffer.get(), m_edgeIndexData.constData());
575 m_edgeGeometryDirty =
false;
578 if (m_edgeInstancesDirty && m_edgeInstanceCount > 0) {
579 int requiredSize = m_edgeInstanceData.size();
580 if (!m_gpu->edgeInstanceBuffer || m_gpu->edgeInstanceBuffer->size() <
static_cast<quint32
>(requiredSize)) {
581 m_gpu->edgeInstanceBuffer.reset(rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::VertexBuffer, requiredSize));
582 m_gpu->edgeInstanceBuffer->create();
584 u->updateDynamicBuffer(m_gpu->edgeInstanceBuffer.get(), 0, requiredSize, m_edgeInstanceData.constData());
585 m_edgeInstancesDirty =
false;
Static scalar-to-colour lookup helpers (Jet, Hot, Bone, Viridis, Cool, RedBlue, MNE) used by every pl...
Instanced connectivity-graph renderable: node spheres and edge cylinders coloured by weight through a...
Node of a connectivity CONNECTIVITYLIB::Network; carries a 3D position and the lists of incident (in ...
Weighted edge between two CONNECTIVITYLIB::NetworkNode instances; stores the full per-frequency weigh...
Functional connectivity metrics (coherence, PLV, cross-correlation, etc.).
2-D display widgets and visualisation helpers (charts, topography, colour maps).
3-D brain visualisation using the Qt RHI rendering backend.
Graph container for one connectivity metric; nodes + weighted edges + threshold/visualisation state.
static QRgb valueToColor(double v, const QString &sMap)
std::unique_ptr< QRhiBuffer > nodeIndexBuffer
std::unique_ptr< QRhiBuffer > nodeVertexBuffer
std::unique_ptr< QRhiBuffer > edgeIndexBuffer
std::unique_ptr< QRhiBuffer > nodeInstanceBuffer
std::unique_ptr< QRhiBuffer > edgeVertexBuffer
std::unique_ptr< QRhiBuffer > edgeInstanceBuffer
QRhiBuffer * edgeInstanceBuffer() const
QRhiBuffer * nodeInstanceBuffer() const
void load(const CONNECTIVITYLIB::Network &network, const QString &sColormap="Viridis")
QRhiBuffer * nodeIndexBuffer() const
void setThreshold(double dThreshold)
void setColormap(const QString &sColormap)
void updateEdgeBuffers(QRhi *rhi, QRhiResourceUpdateBatch *u)
void updateNodeBuffers(QRhi *rhi, QRhiResourceUpdateBatch *u)
QRhiBuffer * edgeVertexBuffer() const
QRhiBuffer * edgeIndexBuffer() const
QRhiBuffer * nodeVertexBuffer() const