76 m_colormap = sColormap;
88 m_network.setThreshold(dThreshold);
97 m_colormap = sColormap;
104void NetworkObject::createNodeGeometry()
106 if (!m_nodeVertexData.isEmpty())
return;
109 const int subdivisions = 1;
110 const float radius = 1.0f;
113 const float t = (1.0f + std::sqrt(5.0f)) / 2.0f;
115 std::vector<QVector3D> vertices = {
116 QVector3D(-1, t, 0).normalized() * radius,
117 QVector3D( 1, t, 0).normalized() * radius,
118 QVector3D(-1, -t, 0).normalized() * radius,
119 QVector3D( 1, -t, 0).normalized() * radius,
120 QVector3D( 0, -1, t).normalized() * radius,
121 QVector3D( 0, 1, t).normalized() * radius,
122 QVector3D( 0, -1, -t).normalized() * radius,
123 QVector3D( 0, 1, -t).normalized() * radius,
124 QVector3D( t, 0, -1).normalized() * radius,
125 QVector3D( t, 0, 1).normalized() * radius,
126 QVector3D(-t, 0, -1).normalized() * radius,
127 QVector3D(-t, 0, 1).normalized() * radius,
130 std::vector<uint32_t> indices = {
131 0,11,5, 0,5,1, 0,1,7, 0,7,10, 0,10,11,
132 1,5,9, 5,11,4, 11,10,2, 10,7,6, 7,1,8,
133 3,9,4, 3,4,2, 3,2,6, 3,6,8, 3,8,9,
134 4,9,5, 2,4,11, 6,2,10, 8,6,7, 9,8,1,
138 for (
int s = 0; s < subdivisions; ++s) {
139 std::vector<uint32_t> newIndices;
140 std::map<uint64_t, uint32_t> midpointCache;
142 auto getMidpoint = [&](uint32_t i0, uint32_t i1) -> uint32_t {
143 uint64_t key = (uint64_t)std::min(i0, i1) << 32 | std::max(i0, i1);
144 auto it = midpointCache.find(key);
145 if (it != midpointCache.end())
return it->second;
147 QVector3D mid = ((vertices[i0] + vertices[i1]) / 2.0f).normalized() * radius;
148 uint32_t idx = (uint32_t)vertices.size();
149 vertices.push_back(mid);
150 midpointCache[key] = idx;
154 for (
size_t i = 0; i < indices.size(); i += 3) {
155 uint32_t a = indices[i], b = indices[i + 1], c = indices[i + 2];
156 uint32_t ab = getMidpoint(a, b);
157 uint32_t bc = getMidpoint(b, c);
158 uint32_t ca = getMidpoint(c, a);
160 newIndices.insert(newIndices.end(), {a, ab, ca});
161 newIndices.insert(newIndices.end(), {b, bc, ab});
162 newIndices.insert(newIndices.end(), {c, ca, bc});
163 newIndices.insert(newIndices.end(), {ab, bc, ca});
166 indices = std::move(newIndices);
170 std::vector<VertexData> vd;
171 vd.reserve(vertices.size());
172 for (
const auto &v : vertices) {
173 QVector3D n = v.normalized();
174 vd.push_back({v.x(), v.y(), v.z(), n.x(), n.y(), n.z()});
177 m_nodeIndexCount = (int)indices.size();
179 m_nodeVertexData.resize(vd.size() *
sizeof(VertexData));
180 memcpy(m_nodeVertexData.data(), vd.data(), m_nodeVertexData.size());
182 m_nodeIndexData.resize(indices.size() *
sizeof(uint32_t));
183 memcpy(m_nodeIndexData.data(), indices.data(), m_nodeIndexData.size());
185 m_nodeGeometryDirty =
true;
190void NetworkObject::createEdgeGeometry()
192 if (!m_edgeVertexData.isEmpty())
return;
195 const int segments = 8;
196 const float radius = 1.0f;
197 const float halfHeight = 0.5f;
199 std::vector<VertexData> vertices;
200 std::vector<uint32_t> indices;
203 vertices.push_back({0, halfHeight, 0, 0, 1, 0});
205 vertices.push_back({0, -halfHeight, 0, 0, -1, 0});
208 for (
int i = 0; i < segments; ++i) {
209 float angle = 2.0f * (float)
M_PI * i / segments;
210 float x = radius * std::cos(angle);
211 float z = radius * std::sin(angle);
213 QVector3D normal(x, 0, z);
217 vertices.push_back({x, halfHeight, z, normal.x(), normal.y(), normal.z()});
219 vertices.push_back({x, -halfHeight, z, normal.x(), normal.y(), normal.z()});
223 int topCapStart = (int)vertices.size();
224 for (
int i = 0; i < segments; ++i) {
225 float angle = 2.0f * (float)
M_PI * i / segments;
226 float x = radius * std::cos(angle);
227 float z = radius * std::sin(angle);
228 vertices.push_back({x, halfHeight, z, 0, 1, 0});
232 int botCapStart = (int)vertices.size();
233 for (
int i = 0; i < segments; ++i) {
234 float angle = 2.0f * (float)
M_PI * i / segments;
235 float x = radius * std::cos(angle);
236 float z = radius * std::sin(angle);
237 vertices.push_back({x, -halfHeight, z, 0, -1, 0});
242 for (
int i = 0; i < segments; ++i) {
243 int next = (i + 1) % segments;
244 int topCur = sideStart + i * 2;
245 int botCur = sideStart + i * 2 + 1;
246 int topNext = sideStart + next * 2;
247 int botNext = sideStart + next * 2 + 1;
249 indices.insert(indices.end(), {(uint32_t)topCur, (uint32_t)topNext, (uint32_t)botCur});
250 indices.insert(indices.end(), {(uint32_t)botCur, (uint32_t)topNext, (uint32_t)botNext});
254 for (
int i = 0; i < segments; ++i) {
255 int next = (i + 1) % segments;
256 indices.push_back(0);
257 indices.push_back(topCapStart + i);
258 indices.push_back(topCapStart + next);
262 for (
int i = 0; i < segments; ++i) {
263 int next = (i + 1) % segments;
264 indices.push_back(1);
265 indices.push_back(botCapStart + next);
266 indices.push_back(botCapStart + i);
269 m_edgeIndexCount = (int)indices.size();
271 m_edgeVertexData.resize(vertices.size() *
sizeof(VertexData));
272 memcpy(m_edgeVertexData.data(), vertices.data(), m_edgeVertexData.size());
274 m_edgeIndexData.resize(indices.size() *
sizeof(uint32_t));
275 memcpy(m_edgeIndexData.data(), indices.data(), m_edgeIndexData.size());
277 m_edgeGeometryDirty =
true;
282void NetworkObject::buildNodeInstances()
284 if (m_network.isEmpty()) {
285 m_nodeInstanceCount = 0;
286 m_nodeInstancesDirty =
true;
290 const auto &nodes = m_network.getNodes();
291 qint16 iMaxDegree = m_network.getMinMaxThresholdedDegrees().second;
292 if (iMaxDegree == 0) iMaxDegree = 1;
294 VisualizationInfo vizInfo = m_network.getVisualizationInfo();
296 std::vector<InstanceData> instances;
297 instances.reserve(nodes.size());
299 for (
int i = 0; i < nodes.size(); ++i) {
300 qint16 degree = nodes[i]->getThresholdedDegree();
301 if (degree == 0)
continue;
303 const RowVectorXf &vert = nodes[i]->getVert();
304 QVector3D pos(vert(0), vert(1), vert(2));
308 float scaleFactor = ((float)degree / (float)iMaxDegree) * (0.005f - 0.0006f) + 0.0006f;
312 m.scale(scaleFactor);
315 const float *mPtr = m.constData();
316 for (
int j = 0; j < 16; ++j) inst.model[j] = mPtr[j];
319 if (vizInfo.
sMethod ==
"Map") {
320 float normalized = (float)degree / (
float)iMaxDegree;
323 float alpha = std::pow(normalized, 4.0f);
324 inst.color[0] = color.redF();
325 inst.color[1] = color.greenF();
326 inst.color[2] = color.blueF();
327 inst.color[3] = alpha;
329 inst.color[0] = vizInfo.
colNodes[0] / 255.0f;
330 inst.color[1] = vizInfo.
colNodes[1] / 255.0f;
331 inst.color[2] = vizInfo.
colNodes[2] / 255.0f;
332 inst.color[3] = vizInfo.
colNodes[3] / 255.0f;
334 inst.isSelected = 0.0f;
336 instances.push_back(inst);
339 m_nodeInstanceCount = (int)instances.size();
340 m_nodeInstanceData.resize(m_nodeInstanceCount *
sizeof(InstanceData));
341 if (m_nodeInstanceCount > 0) {
342 memcpy(m_nodeInstanceData.data(), instances.data(), m_nodeInstanceData.size());
344 m_nodeInstancesDirty =
true;
346 qDebug() <<
"NetworkObject: Built" << m_nodeInstanceCount <<
"node instances";
351void NetworkObject::buildEdgeInstances()
353 if (m_network.isEmpty()) {
354 m_edgeInstanceCount = 0;
355 m_edgeInstancesDirty =
true;
359 const auto &edges = m_network.getThresholdedEdges();
360 const auto &nodes = m_network.getNodes();
362 double dMaxWeight = m_network.getMinMaxThresholdedWeights().second;
363 double dMinWeight = m_network.getMinMaxThresholdedWeights().first;
364 double dWeightRange = dMaxWeight - dMinWeight;
365 if (dWeightRange == 0.0) dWeightRange = 1.0;
367 VisualizationInfo vizInfo = m_network.getVisualizationInfo();
369 std::vector<InstanceData> instances;
370 instances.reserve(edges.size());
372 for (
int i = 0; i < edges.size(); ++i) {
373 auto &edge = edges[i];
374 if (!edge->isActive())
continue;
376 int iStart = edge->getStartNodeID();
377 int iEnd = edge->getEndNodeID();
379 if (iStart < 0 || iStart >= nodes.size() || iEnd < 0 || iEnd >= nodes.size())
continue;
381 const RowVectorXf &vStart = nodes[iStart]->getVert();
382 const RowVectorXf &vEnd = nodes[iEnd]->getVert();
384 QVector3D startPos(vStart(0), vStart(1), vStart(2));
385 QVector3D endPos(vEnd(0), vEnd(1), vEnd(2));
387 if (startPos == endPos)
continue;
389 double dWeight = std::fabs(edge->getWeight());
390 if (dWeight == 0.0)
continue;
392 QVector3D diff = endPos - startPos;
393 QVector3D midPoint = startPos + diff / 2.0f;
394 float length = diff.length();
397 float normalizedWeight = (float)std::fabs((dWeight - dMinWeight) / dWeightRange);
400 float edgeRadius = 0.0001f + normalizedWeight * 0.0009f;
403 m.translate(midPoint);
404 m.rotate(QQuaternion::rotationTo(QVector3D(0, 1, 0), diff.normalized()));
405 m.scale(edgeRadius, length, edgeRadius);
408 const float *mPtr = m.constData();
409 for (
int j = 0; j < 16; ++j) inst.model[j] = mPtr[j];
412 if (vizInfo.
sMethod ==
"Map") {
413 float normalized = (dMaxWeight != 0.0) ? (
float)std::fabs(dWeight / dMaxWeight) : 0.0f;
416 float alpha = std::pow(normalized, 1.5f);
417 inst.color[0] = color.redF();
418 inst.color[1] = color.greenF();
419 inst.color[2] = color.blueF();
420 inst.color[3] = alpha;
422 inst.color[0] = vizInfo.
colEdges[0] / 255.0f;
423 inst.color[1] = vizInfo.
colEdges[1] / 255.0f;
424 inst.color[2] = vizInfo.
colEdges[2] / 255.0f;
425 inst.color[3] = vizInfo.
colEdges[3] / 255.0f;
427 inst.isSelected = 0.0f;
429 instances.push_back(inst);
432 m_edgeInstanceCount = (int)instances.size();
433 m_edgeInstanceData.resize(m_edgeInstanceCount *
sizeof(InstanceData));
434 if (m_edgeInstanceCount > 0) {
435 memcpy(m_edgeInstanceData.data(), instances.data(), m_edgeInstanceData.size());
437 m_edgeInstancesDirty =
true;
439 qDebug() <<
"NetworkObject: Built" << m_edgeInstanceCount <<
"edge instances";
446 if (m_nodeGeometryDirty) {
447 if (!m_gpu->nodeVertexBuffer) {
448 m_gpu->nodeVertexBuffer.reset(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, m_nodeVertexData.size()));
449 m_gpu->nodeVertexBuffer->create();
451 if (!m_gpu->nodeIndexBuffer) {
452 m_gpu->nodeIndexBuffer.reset(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::IndexBuffer, m_nodeIndexData.size()));
453 m_gpu->nodeIndexBuffer->create();
455 u->uploadStaticBuffer(m_gpu->nodeVertexBuffer.get(), m_nodeVertexData.constData());
456 u->uploadStaticBuffer(m_gpu->nodeIndexBuffer.get(), m_nodeIndexData.constData());
457 m_nodeGeometryDirty =
false;
460 if (m_nodeInstancesDirty && m_nodeInstanceCount > 0) {
461 int requiredSize = m_nodeInstanceData.size();
462 if (!m_gpu->nodeInstanceBuffer || m_gpu->nodeInstanceBuffer->size() < requiredSize) {
463 m_gpu->nodeInstanceBuffer.reset(rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::VertexBuffer, requiredSize));
464 m_gpu->nodeInstanceBuffer->create();
466 u->updateDynamicBuffer(m_gpu->nodeInstanceBuffer.get(), 0, requiredSize, m_nodeInstanceData.constData());
467 m_nodeInstancesDirty =
false;
475 if (m_edgeGeometryDirty) {
476 if (!m_gpu->edgeVertexBuffer) {
477 m_gpu->edgeVertexBuffer.reset(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, m_edgeVertexData.size()));
478 m_gpu->edgeVertexBuffer->create();
480 if (!m_gpu->edgeIndexBuffer) {
481 m_gpu->edgeIndexBuffer.reset(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::IndexBuffer, m_edgeIndexData.size()));
482 m_gpu->edgeIndexBuffer->create();
484 u->uploadStaticBuffer(m_gpu->edgeVertexBuffer.get(), m_edgeVertexData.constData());
485 u->uploadStaticBuffer(m_gpu->edgeIndexBuffer.get(), m_edgeIndexData.constData());
486 m_edgeGeometryDirty =
false;
489 if (m_edgeInstancesDirty && m_edgeInstanceCount > 0) {
490 int requiredSize = m_edgeInstanceData.size();
491 if (!m_gpu->edgeInstanceBuffer || m_gpu->edgeInstanceBuffer->size() < requiredSize) {
492 m_gpu->edgeInstanceBuffer.reset(rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::VertexBuffer, requiredSize));
493 m_gpu->edgeInstanceBuffer->create();
495 u->updateDynamicBuffer(m_gpu->edgeInstanceBuffer.get(), 0, requiredSize, m_edgeInstanceData.constData());
496 m_edgeInstancesDirty =
false;
Instanced connectivity-graph renderable: node spheres and edge cylinders coloured by weight through a...
Static scalar-to-colour lookup helpers (Jet, Hot, Bone, Viridis, Cool, RedBlue, MNE) used by every pl...
Weighted edge between two NetworkNode instances; stores the full per-frequency weight matrix and the ...
Node of a connectivity Network; carries a 3D position and the lists of incident (in / out,...
Functional connectivity metrics (coherence, PLV, cross-correlation, etc.).
2-D display widgets and visualisation helpers (charts, topography, colour maps).
Graph container for one connectivity metric; nodes + weighted edges + threshold/visualisation state.
static QRgb valueToColor(double v, const QString &sMap)
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