48 return {{m_nodeVertexData.constData(),
static_cast<quint32
>(m_nodeVertexData.size()), m_nodeGeometryGeneration},
49 {m_nodeIndexData.constData(),
static_cast<quint32
>(m_nodeIndexData.size()), m_nodeGeometryGeneration},
50 {m_nodeInstanceData.constData(),
static_cast<quint32
>(m_nodeInstanceCount *
sizeof(InstanceData)), m_nodeInstanceGeneration},
57 return {{m_edgeVertexData.constData(),
static_cast<quint32
>(m_edgeVertexData.size()), m_edgeGeometryGeneration},
58 {m_edgeIndexData.constData(),
static_cast<quint32
>(m_edgeIndexData.size()), m_edgeGeometryGeneration},
59 {m_edgeInstanceData.constData(),
static_cast<quint32
>(m_edgeInstanceCount *
sizeof(InstanceData)), m_edgeInstanceGeneration},
69 m_colormap = sColormap;
81 m_network.setThreshold(dThreshold);
90 m_colormap = sColormap;
97void NetworkObject::createNodeGeometry()
99 if (!m_nodeVertexData.isEmpty())
103 const int subdivisions = 1;
104 const float radius = 1.0f;
107 const float t = (1.0f + std::sqrt(5.0f)) / 2.0f;
109 std::vector<QVector3D> vertices = {
110 QVector3D(-1, t, 0).normalized() * radius,
111 QVector3D(1, t, 0).normalized() * radius,
112 QVector3D(-1, -t, 0).normalized() * radius,
113 QVector3D(1, -t, 0).normalized() * radius,
114 QVector3D(0, -1, t).normalized() * radius,
115 QVector3D(0, 1, t).normalized() * radius,
116 QVector3D(0, -1, -t).normalized() * radius,
117 QVector3D(0, 1, -t).normalized() * radius,
118 QVector3D(t, 0, -1).normalized() * radius,
119 QVector3D(t, 0, 1).normalized() * radius,
120 QVector3D(-t, 0, -1).normalized() * radius,
121 QVector3D(-t, 0, 1).normalized() * radius,
124 std::vector<uint32_t> indices = {
188 for (
int s = 0; s < subdivisions; ++s) {
189 std::vector<uint32_t> newIndices;
190 std::map<uint64_t, uint32_t> midpointCache;
192 auto getMidpoint = [&](uint32_t i0, uint32_t i1) -> uint32_t {
193 uint64_t key =
static_cast<uint64_t
>(std::min(i0, i1)) << 32 | std::max(i0, i1);
194 auto it = midpointCache.find(key);
195 if (it != midpointCache.end())
198 QVector3D mid = ((vertices[i0] + vertices[i1]) / 2.0f).normalized() * radius;
199 uint32_t idx =
static_cast<uint32_t
>(vertices.size());
200 vertices.push_back(mid);
201 midpointCache[key] = idx;
205 for (
size_t i = 0; i < indices.size(); i += 3) {
206 uint32_t a = indices[i], b = indices[i + 1], c = indices[i + 2];
207 uint32_t ab = getMidpoint(a, b);
208 uint32_t bc = getMidpoint(b, c);
209 uint32_t ca = getMidpoint(c, a);
211 newIndices.insert(newIndices.end(), {a, ab, ca});
212 newIndices.insert(newIndices.end(), {b, bc, ab});
213 newIndices.insert(newIndices.end(), {c, ca, bc});
214 newIndices.insert(newIndices.end(), {ab, bc, ca});
217 indices = std::move(newIndices);
221 std::vector<VertexData> vd;
222 vd.reserve(vertices.size());
223 for (
const auto& v : vertices) {
224 QVector3D n = v.normalized();
225 vd.push_back({v.x(), v.y(), v.z(), n.x(), n.y(), n.z()});
228 m_nodeIndexCount =
static_cast<int>(indices.size());
230 m_nodeVertexData.resize(vd.size() *
sizeof(VertexData));
231 memcpy(m_nodeVertexData.data(), vd.data(), m_nodeVertexData.size());
233 m_nodeIndexData.resize(indices.size() *
sizeof(uint32_t));
234 memcpy(m_nodeIndexData.data(), indices.data(), m_nodeIndexData.size());
236 ++m_nodeGeometryGeneration;
241void NetworkObject::createEdgeGeometry()
243 if (!m_edgeVertexData.isEmpty())
247 const int segments = 8;
248 const float radius = 1.0f;
249 const float halfHeight = 0.5f;
251 std::vector<VertexData> vertices;
252 std::vector<uint32_t> indices;
255 vertices.push_back({0, halfHeight, 0, 0, 1, 0});
257 vertices.push_back({0, -halfHeight, 0, 0, -1, 0});
260 for (
int i = 0; i < segments; ++i) {
261 float angle = 2.0f *
static_cast<float>(M_PI) * i / segments;
262 float x = radius * std::cos(angle);
263 float z = radius * std::sin(angle);
265 QVector3D normal(x, 0, z);
269 vertices.push_back({x, halfHeight, z, normal.x(), normal.y(), normal.z()});
271 vertices.push_back({x, -halfHeight, z, normal.x(), normal.y(), normal.z()});
275 int topCapStart =
static_cast<int>(vertices.size());
276 for (
int i = 0; i < segments; ++i) {
277 float angle = 2.0f *
static_cast<float>(M_PI) * i / segments;
278 float x = radius * std::cos(angle);
279 float z = radius * std::sin(angle);
280 vertices.push_back({x, halfHeight, z, 0, 1, 0});
284 int botCapStart =
static_cast<int>(vertices.size());
285 for (
int i = 0; i < segments; ++i) {
286 float angle = 2.0f *
static_cast<float>(M_PI) * i / segments;
287 float x = radius * std::cos(angle);
288 float z = radius * std::sin(angle);
289 vertices.push_back({x, -halfHeight, z, 0, -1, 0});
294 for (
int i = 0; i < segments; ++i) {
295 int next = (i + 1) % segments;
296 int topCur = sideStart + i * 2;
297 int botCur = sideStart + i * 2 + 1;
298 int topNext = sideStart + next * 2;
299 int botNext = sideStart + next * 2 + 1;
301 indices.insert(indices.end(), {static_cast<uint32_t>(topCur), static_cast<uint32_t>(topNext), static_cast<uint32_t>(botCur)});
302 indices.insert(indices.end(), {static_cast<uint32_t>(botCur), static_cast<uint32_t>(topNext), static_cast<uint32_t>(botNext)});
306 for (
int i = 0; i < segments; ++i) {
307 int next = (i + 1) % segments;
308 indices.push_back(0);
309 indices.push_back(topCapStart + i);
310 indices.push_back(topCapStart + next);
314 for (
int i = 0; i < segments; ++i) {
315 int next = (i + 1) % segments;
316 indices.push_back(1);
317 indices.push_back(botCapStart + next);
318 indices.push_back(botCapStart + i);
321 m_edgeIndexCount =
static_cast<int>(indices.size());
323 m_edgeVertexData.resize(vertices.size() *
sizeof(VertexData));
324 memcpy(m_edgeVertexData.data(), vertices.data(), m_edgeVertexData.size());
326 m_edgeIndexData.resize(indices.size() *
sizeof(uint32_t));
327 memcpy(m_edgeIndexData.data(), indices.data(), m_edgeIndexData.size());
329 ++m_edgeGeometryGeneration;
334void NetworkObject::buildNodeInstances()
336 if (m_network.isEmpty()) {
337 m_nodeInstanceCount = 0;
338 ++m_nodeInstanceGeneration;
342 const auto& nodes = m_network.getNodes();
343 qint16 iMaxDegree = m_network.getMinMaxThresholdedDegrees().second;
347 VisualizationInfo vizInfo = m_network.getVisualizationInfo();
349 std::vector<InstanceData> instances;
350 instances.reserve(nodes.size());
352 for (
int i = 0; i < nodes.size(); ++i) {
353 qint16 degree = nodes[i]->getThresholdedDegree();
357 const RowVectorXf& vert = nodes[i]->getVert();
358 QVector3D pos(vert(0), vert(1), vert(2));
362 float scaleFactor = (
static_cast<float>(degree) /
static_cast<float>(iMaxDegree)) * (0.005f - 0.0006f) + 0.0006f;
366 m.scale(scaleFactor);
369 const float* mPtr = m.constData();
370 for (
int j = 0; j < 16; ++j)
371 inst.model[j] = mPtr[j];
374 if (vizInfo.
sMethod ==
"Map") {
375 float normalized =
static_cast<float>(degree) /
static_cast<float>(iMaxDegree);
378 float alpha = std::pow(normalized, 4.0f);
379 inst.color[0] = color.redF();
380 inst.color[1] = color.greenF();
381 inst.color[2] = color.blueF();
382 inst.color[3] = alpha;
384 inst.color[0] = vizInfo.
colNodes[0] / 255.0f;
385 inst.color[1] = vizInfo.
colNodes[1] / 255.0f;
386 inst.color[2] = vizInfo.
colNodes[2] / 255.0f;
387 inst.color[3] = vizInfo.
colNodes[3] / 255.0f;
389 inst.isSelected = 0.0f;
391 instances.push_back(inst);
394 m_nodeInstanceCount =
static_cast<int>(instances.size());
395 m_nodeInstanceData.resize(m_nodeInstanceCount *
sizeof(InstanceData));
396 if (m_nodeInstanceCount > 0) {
397 memcpy(m_nodeInstanceData.data(), instances.data(), m_nodeInstanceData.size());
399 ++m_nodeInstanceGeneration;
401 qDebug() <<
"NetworkObject: Built" << m_nodeInstanceCount <<
"node instances";
406void NetworkObject::buildEdgeInstances()
408 if (m_network.isEmpty()) {
409 m_edgeInstanceCount = 0;
410 ++m_edgeInstanceGeneration;
414 const auto& edges = m_network.getThresholdedEdges();
415 const auto& nodes = m_network.getNodes();
417 double dMaxWeight = m_network.getMinMaxThresholdedWeights().second;
418 double dMinWeight = m_network.getMinMaxThresholdedWeights().first;
419 double dWeightRange = dMaxWeight - dMinWeight;
420 if (dWeightRange == 0.0)
423 VisualizationInfo vizInfo = m_network.getVisualizationInfo();
425 std::vector<InstanceData> instances;
426 instances.reserve(edges.size());
428 for (
int i = 0; i < edges.size(); ++i) {
429 auto& edge = edges[i];
430 if (!edge->isActive())
433 int iStart = edge->getStartNodeID();
434 int iEnd = edge->getEndNodeID();
436 if (iStart < 0 || iStart >= nodes.size() || iEnd < 0 || iEnd >= nodes.size())
439 const RowVectorXf& vStart = nodes[iStart]->getVert();
440 const RowVectorXf& vEnd = nodes[iEnd]->getVert();
442 QVector3D startPos(vStart(0), vStart(1), vStart(2));
443 QVector3D endPos(vEnd(0), vEnd(1), vEnd(2));
445 if (startPos == endPos)
448 double dWeight = std::fabs(edge->getWeight());
452 QVector3D diff = endPos - startPos;
453 QVector3D midPoint = startPos + diff / 2.0f;
454 float length = diff.length();
457 float normalizedWeight =
static_cast<float>(std::fabs((dWeight - dMinWeight) / dWeightRange));
460 float edgeRadius = 0.0001f + normalizedWeight * 0.0009f;
463 m.translate(midPoint);
464 m.rotate(QQuaternion::rotationTo(QVector3D(0, 1, 0), diff.normalized()));
465 m.scale(edgeRadius, length, edgeRadius);
468 const float* mPtr = m.constData();
469 for (
int j = 0; j < 16; ++j)
470 inst.model[j] = mPtr[j];
473 if (vizInfo.
sMethod ==
"Map") {
474 float normalized = (dMaxWeight != 0.0) ?
static_cast<float>(std::fabs(dWeight / dMaxWeight)) : 0.0f;
477 float alpha = std::pow(normalized, 1.5f);
478 inst.color[0] = color.redF();
479 inst.color[1] = color.greenF();
480 inst.color[2] = color.blueF();
481 inst.color[3] = alpha;
483 inst.color[0] = vizInfo.
colEdges[0] / 255.0f;
484 inst.color[1] = vizInfo.
colEdges[1] / 255.0f;
485 inst.color[2] = vizInfo.
colEdges[2] / 255.0f;
486 inst.color[3] = vizInfo.
colEdges[3] / 255.0f;
488 inst.isSelected = 0.0f;
490 instances.push_back(inst);
493 m_edgeInstanceCount =
static_cast<int>(instances.size());
494 m_edgeInstanceData.resize(m_edgeInstanceCount *
sizeof(InstanceData));
495 if (m_edgeInstanceCount > 0) {
496 memcpy(m_edgeInstanceData.data(), instances.data(), m_edgeInstanceData.size());
498 ++m_edgeInstanceGeneration;
500 qDebug() <<
"NetworkObject: Built" << m_edgeInstanceCount <<
"edge instances";
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)
CPU streams of an instanced mesh: one shape (vertices, indices) drawn once per instance.
InstancedStreams edgeStreams() const
InstancedStreams nodeStreams() const
void load(const CONNECTIVITYLIB::Network &network, const QString &sColormap="Viridis")
void setThreshold(double dThreshold)
void setColormap(const QString &sColormap)