98 m_colormap = sColormap;
100 createNodeGeometry();
101 createEdgeGeometry();
102 buildNodeInstances();
103 buildEdgeInstances();
110 m_network.setThreshold(dThreshold);
111 buildNodeInstances();
112 buildEdgeInstances();
119 m_colormap = sColormap;
120 buildNodeInstances();
121 buildEdgeInstances();
126void NetworkObject::createNodeGeometry()
128 if (!m_nodeVertexData.isEmpty())
return;
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 = {
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,
160 for (
int s = 0; s < subdivisions; ++s) {
161 std::vector<uint32_t> newIndices;
162 std::map<uint64_t, uint32_t> midpointCache;
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;
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;
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);
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});
188 indices = std::move(newIndices);
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()});
199 m_nodeIndexCount = (int)indices.size();
201 m_nodeVertexData.resize(vd.size() *
sizeof(VertexData));
202 memcpy(m_nodeVertexData.data(), vd.data(), m_nodeVertexData.size());
204 m_nodeIndexData.resize(indices.size() *
sizeof(uint32_t));
205 memcpy(m_nodeIndexData.data(), indices.data(), m_nodeIndexData.size());
207 m_nodeGeometryDirty =
true;
212void NetworkObject::createEdgeGeometry()
214 if (!m_edgeVertexData.isEmpty())
return;
217 const int segments = 8;
218 const float radius = 1.0f;
219 const float halfHeight = 0.5f;
221 std::vector<VertexData> vertices;
222 std::vector<uint32_t> indices;
225 vertices.push_back({0, halfHeight, 0, 0, 1, 0});
227 vertices.push_back({0, -halfHeight, 0, 0, -1, 0});
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);
235 QVector3D normal(x, 0, z);
239 vertices.push_back({x, halfHeight, z, normal.x(), normal.y(), normal.z()});
241 vertices.push_back({x, -halfHeight, z, normal.x(), normal.y(), normal.z()});
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});
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});
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;
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});
276 for (
int i = 0; i < segments; ++i) {
277 int next = (i + 1) % segments;
278 indices.push_back(0);
279 indices.push_back(topCapStart + i);
280 indices.push_back(topCapStart + next);
284 for (
int i = 0; i < segments; ++i) {
285 int next = (i + 1) % segments;
286 indices.push_back(1);
287 indices.push_back(botCapStart + next);
288 indices.push_back(botCapStart + i);
291 m_edgeIndexCount = (int)indices.size();
293 m_edgeVertexData.resize(vertices.size() *
sizeof(VertexData));
294 memcpy(m_edgeVertexData.data(), vertices.data(), m_edgeVertexData.size());
296 m_edgeIndexData.resize(indices.size() *
sizeof(uint32_t));
297 memcpy(m_edgeIndexData.data(), indices.data(), m_edgeIndexData.size());
299 m_edgeGeometryDirty =
true;
304void NetworkObject::buildNodeInstances()
306 if (m_network.isEmpty()) {
307 m_nodeInstanceCount = 0;
308 m_nodeInstancesDirty =
true;
312 const auto &nodes = m_network.getNodes();
313 qint16 iMaxDegree = m_network.getMinMaxThresholdedDegrees().second;
314 if (iMaxDegree == 0) iMaxDegree = 1;
316 VisualizationInfo vizInfo = m_network.getVisualizationInfo();
318 std::vector<InstanceData> instances;
319 instances.reserve(nodes.size());
321 for (
int i = 0; i < nodes.size(); ++i) {
322 qint16 degree = nodes[i]->getThresholdedDegree();
323 if (degree == 0)
continue;
325 const RowVectorXf &vert = nodes[i]->getVert();
326 QVector3D pos(vert(0), vert(1), vert(2));
330 float scaleFactor = ((float)degree / (float)iMaxDegree) * (0.005f - 0.0006f) + 0.0006f;
334 m.scale(scaleFactor);
337 const float *mPtr = m.constData();
338 for (
int j = 0; j < 16; ++j) inst.model[j] = mPtr[j];
341 if (vizInfo.
sMethod ==
"Map") {
342 float normalized = (float)degree / (
float)iMaxDegree;
345 float alpha = std::pow(normalized, 4.0f);
346 inst.color[0] = color.redF();
347 inst.color[1] = color.greenF();
348 inst.color[2] = color.blueF();
349 inst.color[3] = alpha;
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;
356 inst.isSelected = 0.0f;
358 instances.push_back(inst);
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());
366 m_nodeInstancesDirty =
true;
368 qDebug() <<
"NetworkObject: Built" << m_nodeInstanceCount <<
"node instances";
373void NetworkObject::buildEdgeInstances()
375 if (m_network.isEmpty()) {
376 m_edgeInstanceCount = 0;
377 m_edgeInstancesDirty =
true;
381 const auto &edges = m_network.getThresholdedEdges();
382 const auto &nodes = m_network.getNodes();
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;
389 VisualizationInfo vizInfo = m_network.getVisualizationInfo();
391 std::vector<InstanceData> instances;
392 instances.reserve(edges.size());
394 for (
int i = 0; i < edges.size(); ++i) {
395 auto &edge = edges[i];
396 if (!edge->isActive())
continue;
398 int iStart = edge->getStartNodeID();
399 int iEnd = edge->getEndNodeID();
401 if (iStart < 0 || iStart >= nodes.size() || iEnd < 0 || iEnd >= nodes.size())
continue;
403 const RowVectorXf &vStart = nodes[iStart]->getVert();
404 const RowVectorXf &vEnd = nodes[iEnd]->getVert();
406 QVector3D startPos(vStart(0), vStart(1), vStart(2));
407 QVector3D endPos(vEnd(0), vEnd(1), vEnd(2));
409 if (startPos == endPos)
continue;
411 double dWeight = std::fabs(edge->getWeight());
412 if (dWeight == 0.0)
continue;
414 QVector3D diff = endPos - startPos;
415 QVector3D midPoint = startPos + diff / 2.0f;
416 float length = diff.length();
419 float normalizedWeight = (float)std::fabs((dWeight - dMinWeight) / dWeightRange);
422 float edgeRadius = 0.0001f + normalizedWeight * 0.0009f;
425 m.translate(midPoint);
426 m.rotate(QQuaternion::rotationTo(QVector3D(0, 1, 0), diff.normalized()));
427 m.scale(edgeRadius, length, edgeRadius);
430 const float *mPtr = m.constData();
431 for (
int j = 0; j < 16; ++j) inst.model[j] = mPtr[j];
434 if (vizInfo.
sMethod ==
"Map") {
435 float normalized = (dMaxWeight != 0.0) ? (
float)std::fabs(dWeight / dMaxWeight) : 0.0f;
438 float alpha = std::pow(normalized, 1.5f);
439 inst.color[0] = color.redF();
440 inst.color[1] = color.greenF();
441 inst.color[2] = color.blueF();
442 inst.color[3] = alpha;
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;
449 inst.isSelected = 0.0f;
451 instances.push_back(inst);
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());
459 m_edgeInstancesDirty =
true;
461 qDebug() <<
"NetworkObject: Built" << m_edgeInstanceCount <<
"edge instances";
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();
473 if (!m_gpu->nodeIndexBuffer) {
474 m_gpu->nodeIndexBuffer.reset(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::IndexBuffer, m_nodeIndexData.size()));
475 m_gpu->nodeIndexBuffer->create();
477 u->uploadStaticBuffer(m_gpu->nodeVertexBuffer.get(), m_nodeVertexData.constData());
478 u->uploadStaticBuffer(m_gpu->nodeIndexBuffer.get(), m_nodeIndexData.constData());
479 m_nodeGeometryDirty =
false;
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();
488 u->updateDynamicBuffer(m_gpu->nodeInstanceBuffer.get(), 0, requiredSize, m_nodeInstanceData.constData());
489 m_nodeInstancesDirty =
false;
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();
502 if (!m_gpu->edgeIndexBuffer) {
503 m_gpu->edgeIndexBuffer.reset(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::IndexBuffer, m_edgeIndexData.size()));
504 m_gpu->edgeIndexBuffer->create();
506 u->uploadStaticBuffer(m_gpu->edgeVertexBuffer.get(), m_edgeVertexData.constData());
507 u->uploadStaticBuffer(m_gpu->edgeIndexBuffer.get(), m_edgeIndexData.constData());
508 m_edgeGeometryDirty =
false;
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();
517 u->updateDynamicBuffer(m_gpu->edgeInstanceBuffer.get(), 0, requiredSize, m_edgeInstanceData.constData());
518 m_edgeInstancesDirty =
false;
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...
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