19#include <Eigen/Geometry>
34Eigen::Matrix3f extractRotation(
const QMatrix4x4& m)
37 for (
int r = 0; r < 3; ++r)
38 for (
int c = 0; c < 3; ++c)
49void MeshFactory::buildIcosphere(QVector<Eigen::Vector3f>& vertices,
50 QVector<Eigen::Vector3i>& faces,
54 const float phi = (1.0f + std::sqrt(5.0f)) / 2.0f;
57 vertices.reserve(12 + 30 * subdivisions);
58 vertices << Eigen::Vector3f(-1, phi, 0) << Eigen::Vector3f(1, phi, 0)
59 << Eigen::Vector3f(-1, -phi, 0) << Eigen::Vector3f(1, -phi, 0)
60 << Eigen::Vector3f(0, -1, phi) << Eigen::Vector3f(0, 1, phi)
61 << Eigen::Vector3f(0, -1, -phi) << Eigen::Vector3f(0, 1, -phi)
62 << Eigen::Vector3f(phi, 0, -1) << Eigen::Vector3f(phi, 0, 1)
63 << Eigen::Vector3f(-phi, 0, -1) << Eigen::Vector3f(-phi, 0, 1);
64 for (
auto& v : vertices)
69 faces << Eigen::Vector3i(0, 11, 5) << Eigen::Vector3i(0, 5, 1)
70 << Eigen::Vector3i(0, 1, 7) << Eigen::Vector3i(0, 7, 10)
71 << Eigen::Vector3i(0, 10, 11) << Eigen::Vector3i(1, 5, 9)
72 << Eigen::Vector3i(5, 11, 4) << Eigen::Vector3i(11, 10, 2)
73 << Eigen::Vector3i(10, 7, 6) << Eigen::Vector3i(7, 1, 8)
74 << Eigen::Vector3i(3, 9, 4) << Eigen::Vector3i(3, 4, 2)
75 << Eigen::Vector3i(3, 2, 6) << Eigen::Vector3i(3, 6, 8)
76 << Eigen::Vector3i(3, 8, 9) << Eigen::Vector3i(4, 9, 5)
77 << Eigen::Vector3i(2, 4, 11) << Eigen::Vector3i(6, 2, 10)
78 << Eigen::Vector3i(8, 6, 7) << Eigen::Vector3i(9, 8, 1);
81 for (
int s = 0; s < subdivisions; ++s) {
82 QMap<QPair<int, int>,
int> cache;
83 auto midpoint = [&](
int a,
int b) ->
int {
84 auto key = qMakePair(qMin(a, b), qMax(a, b));
85 if (cache.contains(key))
87 int idx = vertices.size();
88 vertices.append((vertices[a] + vertices[b]).normalized());
93 QVector<Eigen::Vector3i> newFaces;
94 newFaces.reserve(faces.size() * 4);
95 for (
const auto& f : faces) {
96 int ab = midpoint(f(0), f(1));
97 int bc = midpoint(f(1), f(2));
98 int ca = midpoint(f(2), f(0));
99 newFaces << Eigen::Vector3i(f(0), ab, ca)
100 << Eigen::Vector3i(f(1), bc, ab)
101 << Eigen::Vector3i(f(2), ca, bc)
102 << Eigen::Vector3i(ab, bc, ca);
104 faces = std::move(newFaces);
112 QVector<Eigen::Vector3f> verts;
113 QVector<Eigen::Vector3i> faces;
114 buildIcosphere(verts, faces, subdivisions);
125 QVector<Eigen::Vector3f> verts;
126 QVector<Eigen::Vector3i> faces;
127 buildIcosphere(verts, faces, subdivisions);
129 const int nV = verts.size();
130 const int nT = faces.size();
132 Eigen::MatrixX3f rr(nV, 3), nn(nV, 3);
133 for (
int i = 0; i < nV; ++i) {
134 nn(i, 0) = verts[i].x();
135 nn(i, 1) = verts[i].y();
136 nn(i, 2) = verts[i].z();
137 rr(i, 0) = verts[i].x() * radius + center.x();
138 rr(i, 1) = verts[i].y() * radius + center.y();
139 rr(i, 2) = verts[i].z() * radius + center.z();
142 Eigen::MatrixX3i tris(nT, 3);
143 for (
int i = 0; i < nT; ++i)
144 tris.row(i) = faces[i];
146 auto surf = std::make_shared<BrainSurface>();
147 surf->createFromData(rr, nn, tris, color);
154 const QMatrix4x4& orientation,
158 const float hw = size / 2.0f;
159 const float hh = size / 2.0f;
160 const float hd = size * 0.05f;
162 const Eigen::Matrix3f rot = extractRotation(orientation);
164 Eigen::Vector3f corners[8] = {
165 {-hw, -hh, -hd}, {hw, -hh, -hd}, {hw, hh, -hd}, {-hw, hh, -hd}, {-hw, -hh, hd}, {hw, -hh, hd}, {hw, hh, hd}, {-hw, hh, hd}};
166 for (
auto& c : corners)
169 const int faceIndices[6][4] = {
170 {4, 5, 6, 7}, {1, 0, 3, 2}, {3, 7, 6, 2}, {0, 1, 5, 4}, {1, 2, 6, 5}, {0, 4, 7, 3}};
172 Eigen::MatrixX3f rr(24, 3), nn(24, 3);
173 Eigen::MatrixX3i tris(12, 3);
175 for (
int f = 0; f < 6; ++f) {
176 Eigen::Vector3f v0 = corners[faceIndices[f][0]];
177 Eigen::Vector3f v1 = corners[faceIndices[f][1]];
178 Eigen::Vector3f v2 = corners[faceIndices[f][2]];
179 Eigen::Vector3f e1 = v1 - v0;
180 Eigen::Vector3f e2 = v2 - v0;
181 Eigen::Vector3f fn = e1.cross(e2).normalized();
183 for (
int k = 0; k < 4; ++k) {
184 Eigen::Vector3f v = corners[faceIndices[f][k]];
185 rr.row(base + k) << v.x() + center.x(), v.y() + center.y(), v.z() + center.z();
186 nn.row(base + k) = fn;
188 tris.row(f * 2) << base, base + 1, base + 2;
189 tris.row(f * 2 + 1) << base, base + 2, base + 3;
192 auto surf = std::make_shared<BrainSurface>();
193 surf->createFromData(rr, nn, tris, color);
200 const QMatrix4x4& orientation,
204 const Eigen::Matrix3f rot = extractRotation(orientation);
206 const float halfSpan = size * 0.6f;
207 const float sphereR = size * 0.2f;
208 const float rodR = size * 0.08f;
210 QVector<Eigen::Vector3f> allVerts;
211 QVector<Eigen::Vector3f> allNorms;
212 QVector<Eigen::Vector3i> allTris;
215 auto appendSphere = [&](
const QVector3D& pos,
float radius) {
216 QVector<Eigen::Vector3f> sv;
217 QVector<Eigen::Vector3i> sf;
218 buildIcosphere(sv, sf, 1);
220 const int base = allVerts.size();
221 allVerts.reserve(base + sv.size());
222 allNorms.reserve(base + sv.size());
223 allTris.reserve(allTris.size() + sf.size());
225 for (
const auto& v : sv) {
226 Eigen::Vector3f vn = rot * v;
227 Eigen::Vector3f vp = rot * (v * radius);
228 allVerts.append(Eigen::Vector3f(vp.x() + pos.x(), vp.y() + pos.y(), vp.z() + pos.z()));
229 allNorms.append(vn.normalized());
231 for (
const auto& f : sf) {
232 allTris.append(Eigen::Vector3i(base + f(0), base + f(1), base + f(2)));
237 auto appendRod = [&]() {
238 float hx = rodR, hy = halfSpan, hz = rodR;
239 Eigen::Vector3f corners[8] = {
240 {-hx, -hy, -hz}, {hx, -hy, -hz}, {hx, hy, -hz}, {-hx, hy, -hz}, {-hx, -hy, hz}, {hx, -hy, hz}, {hx, hy, hz}, {-hx, hy, hz}};
241 for (
auto& c : corners)
244 const int faceIdx[6][4] = {
245 {4, 5, 6, 7}, {1, 0, 3, 2}, {3, 7, 6, 2}, {0, 1, 5, 4}, {1, 2, 6, 5}, {0, 4, 7, 3}};
247 const int base = allVerts.size();
248 for (
int f = 0; f < 6; ++f) {
249 Eigen::Vector3f v0 = corners[faceIdx[f][0]];
250 Eigen::Vector3f v1 = corners[faceIdx[f][1]];
251 Eigen::Vector3f v2 = corners[faceIdx[f][2]];
252 Eigen::Vector3f e1 = v1 - v0;
253 Eigen::Vector3f e2 = v2 - v0;
254 Eigen::Vector3f fn = e1.cross(e2).normalized();
255 int faceBase = base + f * 4;
256 for (
int k = 0; k < 4; ++k) {
257 Eigen::Vector3f v = corners[faceIdx[f][k]];
258 allVerts.append(Eigen::Vector3f(v.x() + center.x(), v.y() + center.y(), v.z() + center.z()));
261 allTris.append(Eigen::Vector3i(faceBase, faceBase + 1, faceBase + 2));
262 allTris.append(Eigen::Vector3i(faceBase, faceBase + 2, faceBase + 3));
267 QVector3D axis(rot(0, 1), rot(1, 1), rot(2, 1));
268 appendSphere(center + axis * halfSpan, sphereR);
269 appendSphere(center - axis * halfSpan, sphereR);
273 Eigen::MatrixX3f rr(allVerts.size(), 3), nn(allNorms.size(), 3);
274 Eigen::MatrixX3i tt(allTris.size(), 3);
275 for (
int i = 0; i < allVerts.size(); ++i) {
276 rr.row(i) = allVerts[i].transpose();
277 nn.row(i) = allNorms[i].transpose();
279 for (
int i = 0; i < allTris.size(); ++i)
280 tt.row(i) = allTris[i];
282 auto surf = std::make_shared<BrainSurface>();
283 surf->createFromData(rr, nn, tt, color);
298 const QVector3D axis = to - from;
299 const float length = axis.length();
301 return std::make_shared<BrainSurface>();
303 const QVector3D axisN = axis / length;
306 QVector3D u = QVector3D::crossProduct(axisN, QVector3D(0, 0, 1));
307 if (u.lengthSquared() < 1e-6f)
308 u = QVector3D::crossProduct(axisN, QVector3D(1, 0, 0));
310 const QVector3D v = QVector3D::crossProduct(axisN, u).normalized();
313 const int nVerts = 2 * sides + 2;
314 const int nTris = 4 * sides;
316 Eigen::MatrixX3f verts(nVerts, 3);
317 Eigen::MatrixX3f norms(nVerts, 3);
318 Eigen::MatrixX3i tris(nTris, 3);
320 constexpr float kTwoPi = 2.0f * 3.14159265358979f;
323 for (
int i = 0; i < sides; ++i) {
324 const float angle = kTwoPi *
static_cast<float>(i) /
static_cast<float>(sides);
325 const float ca = std::cos(angle);
326 const float sa = std::sin(angle);
327 const QVector3D normal = (u * ca + v * sa);
328 const QVector3D pos = from + normal * radius;
329 verts(i, 0) = pos.x();
330 verts(i, 1) = pos.y();
331 verts(i, 2) = pos.z();
332 norms(i, 0) = normal.x();
333 norms(i, 1) = normal.y();
334 norms(i, 2) = normal.z();
338 for (
int i = 0; i < sides; ++i) {
339 const float angle = kTwoPi *
static_cast<float>(i) /
static_cast<float>(sides);
340 const float ca = std::cos(angle);
341 const float sa = std::sin(angle);
342 const QVector3D normal = (u * ca + v * sa);
343 const QVector3D pos = to + normal * radius;
344 verts(sides + i, 0) = pos.x();
345 verts(sides + i, 1) = pos.y();
346 verts(sides + i, 2) = pos.z();
347 norms(sides + i, 0) = normal.x();
348 norms(sides + i, 1) = normal.y();
349 norms(sides + i, 2) = normal.z();
353 const int bottomCenter = 2 * sides;
354 const int topCenter = 2 * sides + 1;
355 verts(bottomCenter, 0) = from.x();
356 verts(bottomCenter, 1) = from.y();
357 verts(bottomCenter, 2) = from.z();
358 norms(bottomCenter, 0) = -axisN.x();
359 norms(bottomCenter, 1) = -axisN.y();
360 norms(bottomCenter, 2) = -axisN.z();
361 verts(topCenter, 0) = to.x();
362 verts(topCenter, 1) = to.y();
363 verts(topCenter, 2) = to.z();
364 norms(topCenter, 0) = axisN.x();
365 norms(topCenter, 1) = axisN.y();
366 norms(topCenter, 2) = axisN.z();
370 for (
int i = 0; i < sides; ++i) {
372 const int i1 = (i + 1) % sides;
373 const int i2 = sides + i;
374 const int i3 = sides + (i + 1) % sides;
386 for (
int i = 0; i < sides; ++i) {
387 tris(t, 0) = bottomCenter;
388 tris(t, 1) = (i + 1) % sides;
394 for (
int i = 0; i < sides; ++i) {
395 tris(t, 0) = topCenter;
396 tris(t, 1) = sides + i;
397 tris(t, 2) = sides + (i + 1) % sides;
401 auto surf = std::make_shared<BrainSurface>();
402 surf->createFromData(verts, norms, tris, color);
413 if (positions.isEmpty())
414 return std::make_shared<BrainSurface>();
417 QVector<Eigen::Vector3f> tmplVerts;
418 QVector<Eigen::Vector3i> tmplFaces;
419 buildIcosphere(tmplVerts, tmplFaces, subdivisions);
421 const int nVPerPt = tmplVerts.size();
422 const int nTPerPt = tmplFaces.size();
423 const int nPts = positions.size();
426 Eigen::MatrixX3f templateV(nVPerPt, 3), templateN(nVPerPt, 3);
427 for (
int i = 0; i < nVPerPt; ++i) {
428 templateN(i, 0) = tmplVerts[i].x();
429 templateN(i, 1) = tmplVerts[i].y();
430 templateN(i, 2) = tmplVerts[i].z();
431 templateV(i, 0) = tmplVerts[i].x() * radius;
432 templateV(i, 1) = tmplVerts[i].y() * radius;
433 templateV(i, 2) = tmplVerts[i].z() * radius;
435 Eigen::MatrixX3i templateT(nTPerPt, 3);
436 for (
int i = 0; i < nTPerPt; ++i)
437 templateT.row(i) = tmplFaces[i];
440 Eigen::MatrixX3f allVerts(nPts * nVPerPt, 3);
441 Eigen::MatrixX3f allNorms(nPts * nVPerPt, 3);
442 Eigen::MatrixX3i allTris(nPts * nTPerPt, 3);
444 for (
int p = 0; p < nPts; ++p) {
445 const QVector3D& pos = positions[p];
446 const int vOff = p * nVPerPt;
447 const int tOff = p * nTPerPt;
448 for (
int iv = 0; iv < nVPerPt; ++iv) {
449 allVerts(vOff + iv, 0) = templateV(iv, 0) + pos.x();
450 allVerts(vOff + iv, 1) = templateV(iv, 1) + pos.y();
451 allVerts(vOff + iv, 2) = templateV(iv, 2) + pos.z();
452 allNorms.row(vOff + iv) = templateN.row(iv);
454 for (
int it = 0; it < nTPerPt; ++it) {
455 allTris(tOff + it, 0) = templateT(it, 0) + vOff;
456 allTris(tOff + it, 1) = templateT(it, 1) + vOff;
457 allTris(tOff + it, 2) = templateT(it, 2) + vOff;
461 auto surf = std::make_shared<BrainSurface>();
462 surf->createFromData(allVerts, allNorms, allTris, color);
Static factory for procedural Qt-RHI meshes (spheres, plates, barbells, cylinders,...
Renderable cortical / BEM mesh with interleaved vertex attributes and Qt-RHI buffer management.
3-D brain visualisation using the Qt RHI rendering backend.
static std::shared_ptr< BrainSurface > createBarbell(const QVector3D ¢er, const QMatrix4x4 &orientation, const QColor &color, float size)
static std::shared_ptr< BrainSurface > createCylinder(const QVector3D &from, const QVector3D &to, float radius, const QColor &color, int sides=12)
static std::shared_ptr< BrainSurface > createBatchedSpheres(const QVector< QVector3D > &positions, float radius, const QColor &color, int subdivisions=1)
static std::shared_ptr< BrainSurface > createSphere(const QVector3D ¢er, float radius, const QColor &color, int subdivisions=1)
static int sphereVertexCount(int subdivisions=1)
static std::shared_ptr< BrainSurface > createPlate(const QVector3D ¢er, const QMatrix4x4 &orientation, const QColor &color, float size)