v2.0.0
Loading...
Searching...
No Matches
meshfactory.cpp
Go to the documentation of this file.
1//=============================================================================================================
12
13//=============================================================================================================
14// INCLUDES
15//=============================================================================================================
16
17#include "meshfactory.h"
19#include <Eigen/Geometry>
20#include <QMap>
21#include <QPair>
22#include <cmath>
23
24//=============================================================================================================
25// STATIC HELPERS
26//=============================================================================================================
27
28namespace {
29
30Eigen::Matrix3f extractRotation(const QMatrix4x4 &m)
31{
32 Eigen::Matrix3f rot;
33 for (int r = 0; r < 3; ++r)
34 for (int c = 0; c < 3; ++c)
35 rot(r, c) = m(r, c);
36 return rot;
37}
38
39} // anonymous namespace
40
41//=============================================================================================================
42// DEFINE MEMBER METHODS
43//=============================================================================================================
44
45void MeshFactory::buildIcosphere(QVector<Eigen::Vector3f> &vertices,
46 QVector<Eigen::Vector3i> &faces,
47 int subdivisions)
48{
49 // Golden ratio
50 const float phi = (1.0f + std::sqrt(5.0f)) / 2.0f;
51
52 vertices.clear();
53 vertices.reserve(12 + 30 * subdivisions); // rough estimate
54 vertices << Eigen::Vector3f(-1, phi, 0) << Eigen::Vector3f( 1, phi, 0)
55 << Eigen::Vector3f(-1, -phi, 0) << Eigen::Vector3f( 1, -phi, 0)
56 << Eigen::Vector3f( 0, -1, phi) << Eigen::Vector3f( 0, 1, phi)
57 << Eigen::Vector3f( 0, -1, -phi) << Eigen::Vector3f( 0, 1, -phi)
58 << Eigen::Vector3f( phi, 0, -1) << Eigen::Vector3f( phi, 0, 1)
59 << Eigen::Vector3f(-phi, 0, -1) << Eigen::Vector3f(-phi, 0, 1);
60 for (auto &v : vertices)
61 v.normalize();
62
63 faces.clear();
64 faces.reserve(20);
65 faces << Eigen::Vector3i(0,11,5) << Eigen::Vector3i(0,5,1)
66 << Eigen::Vector3i(0,1,7) << Eigen::Vector3i(0,7,10)
67 << Eigen::Vector3i(0,10,11) << Eigen::Vector3i(1,5,9)
68 << Eigen::Vector3i(5,11,4) << Eigen::Vector3i(11,10,2)
69 << Eigen::Vector3i(10,7,6) << Eigen::Vector3i(7,1,8)
70 << Eigen::Vector3i(3,9,4) << Eigen::Vector3i(3,4,2)
71 << Eigen::Vector3i(3,2,6) << Eigen::Vector3i(3,6,8)
72 << Eigen::Vector3i(3,8,9) << Eigen::Vector3i(4,9,5)
73 << Eigen::Vector3i(2,4,11) << Eigen::Vector3i(6,2,10)
74 << Eigen::Vector3i(8,6,7) << Eigen::Vector3i(9,8,1);
75
76 // Subdivide
77 for (int s = 0; s < subdivisions; ++s) {
78 QMap<QPair<int,int>, int> cache;
79 auto midpoint = [&](int a, int b) -> int {
80 auto key = qMakePair(qMin(a, b), qMax(a, b));
81 if (cache.contains(key))
82 return cache[key];
83 int idx = vertices.size();
84 vertices.append((vertices[a] + vertices[b]).normalized());
85 cache[key] = idx;
86 return idx;
87 };
88
89 QVector<Eigen::Vector3i> newFaces;
90 newFaces.reserve(faces.size() * 4);
91 for (const auto &f : faces) {
92 int ab = midpoint(f(0), f(1));
93 int bc = midpoint(f(1), f(2));
94 int ca = midpoint(f(2), f(0));
95 newFaces << Eigen::Vector3i(f(0), ab, ca)
96 << Eigen::Vector3i(f(1), bc, ab)
97 << Eigen::Vector3i(f(2), ca, bc)
98 << Eigen::Vector3i(ab, bc, ca);
99 }
100 faces = std::move(newFaces);
101 }
102}
103
104//=============================================================================================================
105
107{
108 QVector<Eigen::Vector3f> verts;
109 QVector<Eigen::Vector3i> faces;
110 buildIcosphere(verts, faces, subdivisions);
111 return verts.size();
112}
113
114//=============================================================================================================
115
116std::shared_ptr<BrainSurface> MeshFactory::createSphere(const QVector3D &center,
117 float radius,
118 const QColor &color,
119 int subdivisions)
120{
121 QVector<Eigen::Vector3f> verts;
122 QVector<Eigen::Vector3i> faces;
123 buildIcosphere(verts, faces, subdivisions);
124
125 const int nV = verts.size();
126 const int nT = faces.size();
127
128 Eigen::MatrixX3f rr(nV, 3), nn(nV, 3);
129 for (int i = 0; i < nV; ++i) {
130 nn(i, 0) = verts[i].x();
131 nn(i, 1) = verts[i].y();
132 nn(i, 2) = verts[i].z();
133 rr(i, 0) = verts[i].x() * radius + center.x();
134 rr(i, 1) = verts[i].y() * radius + center.y();
135 rr(i, 2) = verts[i].z() * radius + center.z();
136 }
137
138 Eigen::MatrixX3i tris(nT, 3);
139 for (int i = 0; i < nT; ++i)
140 tris.row(i) = faces[i];
141
142 auto surf = std::make_shared<BrainSurface>();
143 surf->createFromData(rr, nn, tris, color);
144 return surf;
145}
146
147//=============================================================================================================
148
149std::shared_ptr<BrainSurface> MeshFactory::createPlate(const QVector3D &center,
150 const QMatrix4x4 &orientation,
151 const QColor &color,
152 float size)
153{
154 const float hw = size / 2.0f;
155 const float hh = size / 2.0f;
156 const float hd = size * 0.05f; // thin plate
157
158 const Eigen::Matrix3f rot = extractRotation(orientation);
159
160 Eigen::Vector3f corners[8] = {
161 {-hw, -hh, -hd}, { hw, -hh, -hd}, { hw, hh, -hd}, {-hw, hh, -hd},
162 {-hw, -hh, hd}, { hw, -hh, hd}, { hw, hh, hd}, {-hw, hh, hd}
163 };
164 for (auto &c : corners)
165 c = rot * c;
166
167 const int faceIndices[6][4] = {
168 {4,5,6,7}, {1,0,3,2},
169 {3,7,6,2}, {0,1,5,4},
170 {1,2,6,5}, {0,4,7,3}
171 };
172
173 Eigen::MatrixX3f rr(24, 3), nn(24, 3);
174 Eigen::MatrixX3i tris(12, 3);
175
176 for (int f = 0; f < 6; ++f) {
177 Eigen::Vector3f v0 = corners[faceIndices[f][0]];
178 Eigen::Vector3f v1 = corners[faceIndices[f][1]];
179 Eigen::Vector3f v2 = corners[faceIndices[f][2]];
180 Eigen::Vector3f e1 = v1 - v0;
181 Eigen::Vector3f e2 = v2 - v0;
182 Eigen::Vector3f fn = e1.cross(e2).normalized();
183 int base = f * 4;
184 for (int k = 0; k < 4; ++k) {
185 Eigen::Vector3f v = corners[faceIndices[f][k]];
186 rr.row(base + k) << v.x() + center.x(), v.y() + center.y(), v.z() + center.z();
187 nn.row(base + k) = fn;
188 }
189 tris.row(f * 2) << base, base + 1, base + 2;
190 tris.row(f * 2 + 1) << base, base + 2, base + 3;
191 }
192
193 auto surf = std::make_shared<BrainSurface>();
194 surf->createFromData(rr, nn, tris, color);
195 return surf;
196}
197
198//=============================================================================================================
199
200std::shared_ptr<BrainSurface> MeshFactory::createBarbell(const QVector3D &center,
201 const QMatrix4x4 &orientation,
202 const QColor &color,
203 float size)
204{
205 const Eigen::Matrix3f rot = extractRotation(orientation);
206
207 const float halfSpan = size * 0.6f;
208 const float sphereR = size * 0.2f;
209 const float rodR = size * 0.08f;
210
211 QVector<Eigen::Vector3f> allVerts;
212 QVector<Eigen::Vector3f> allNorms;
213 QVector<Eigen::Vector3i> allTris;
214
215 // ── Helper: append a sphere ────────────────────────────────────────
216 auto appendSphere = [&](const QVector3D &pos, float radius) {
217 QVector<Eigen::Vector3f> sv;
218 QVector<Eigen::Vector3i> sf;
219 buildIcosphere(sv, sf, 1); // single subdivision
220
221 const int base = allVerts.size();
222 allVerts.reserve(base + sv.size());
223 allNorms.reserve(base + sv.size());
224 allTris.reserve(allTris.size() + sf.size());
225
226 for (const auto &v : sv) {
227 Eigen::Vector3f vn = rot * v;
228 Eigen::Vector3f vp = rot * (v * radius);
229 allVerts.append(Eigen::Vector3f(vp.x() + pos.x(), vp.y() + pos.y(), vp.z() + pos.z()));
230 allNorms.append(vn.normalized());
231 }
232 for (const auto &f : sf) {
233 allTris.append(Eigen::Vector3i(base + f(0), base + f(1), base + f(2)));
234 }
235 };
236
237 // ── Helper: append a box-rod ───────────────────────────────────────
238 auto appendRod = [&]() {
239 float hx = rodR, hy = halfSpan, hz = rodR;
240 Eigen::Vector3f corners[8] = {
241 {-hx, -hy, -hz}, { hx, -hy, -hz}, { hx, hy, -hz}, {-hx, hy, -hz},
242 {-hx, -hy, hz}, { hx, -hy, hz}, { hx, hy, hz}, {-hx, hy, hz}
243 };
244 for (auto &c : corners) c = rot * c;
245
246 const int faceIdx[6][4] = {
247 {4,5,6,7}, {1,0,3,2}, {3,7,6,2}, {0,1,5,4}, {1,2,6,5}, {0,4,7,3}
248 };
249
250 const int base = allVerts.size();
251 for (int f = 0; f < 6; ++f) {
252 Eigen::Vector3f v0 = corners[faceIdx[f][0]];
253 Eigen::Vector3f v1 = corners[faceIdx[f][1]];
254 Eigen::Vector3f v2 = corners[faceIdx[f][2]];
255 Eigen::Vector3f e1 = v1 - v0;
256 Eigen::Vector3f e2 = v2 - v0;
257 Eigen::Vector3f fn = e1.cross(e2).normalized();
258 int faceBase = base + f * 4;
259 for (int k = 0; k < 4; ++k) {
260 Eigen::Vector3f v = corners[faceIdx[f][k]];
261 allVerts.append(Eigen::Vector3f(v.x() + center.x(), v.y() + center.y(), v.z() + center.z()));
262 allNorms.append(fn);
263 }
264 allTris.append(Eigen::Vector3i(faceBase, faceBase + 1, faceBase + 2));
265 allTris.append(Eigen::Vector3i(faceBase, faceBase + 2, faceBase + 3));
266 }
267 };
268
269 // Build the barbell
270 QVector3D axis(rot(0,1), rot(1,1), rot(2,1));
271 appendSphere(center + axis * halfSpan, sphereR);
272 appendSphere(center - axis * halfSpan, sphereR);
273 appendRod();
274
275 // Pack into Eigen matrices
276 Eigen::MatrixX3f rr(allVerts.size(), 3), nn(allNorms.size(), 3);
277 Eigen::MatrixX3i tt(allTris.size(), 3);
278 for (int i = 0; i < allVerts.size(); ++i) {
279 rr.row(i) = allVerts[i].transpose();
280 nn.row(i) = allNorms[i].transpose();
281 }
282 for (int i = 0; i < allTris.size(); ++i)
283 tt.row(i) = allTris[i];
284
285 auto surf = std::make_shared<BrainSurface>();
286 surf->createFromData(rr, nn, tt, color);
287 return surf;
288}
289
290//=============================================================================================================
291
292std::shared_ptr<BrainSurface> MeshFactory::createCylinder(const QVector3D &from,
293 const QVector3D &to,
294 float radius,
295 const QColor &color,
296 int sides)
297{
298 if (sides < 3) sides = 3;
299
300 const QVector3D axis = to - from;
301 const float length = axis.length();
302 if (length < 1e-8f)
303 return std::make_shared<BrainSurface>();
304
305 const QVector3D axisN = axis / length;
306
307 // Build an orthonormal basis (axisN, u, v)
308 QVector3D u = QVector3D::crossProduct(axisN, QVector3D(0, 0, 1));
309 if (u.lengthSquared() < 1e-6f)
310 u = QVector3D::crossProduct(axisN, QVector3D(1, 0, 0));
311 u.normalize();
312 const QVector3D v = QVector3D::crossProduct(axisN, u).normalized();
313
314 // Two rings of vertices (bottom = from, top = to) + 2 center caps
315 const int nVerts = 2 * sides + 2;
316 const int nTris = 4 * sides; // 2 * sides for barrel + sides for each cap
317
318 Eigen::MatrixX3f verts(nVerts, 3);
319 Eigen::MatrixX3f norms(nVerts, 3);
320 Eigen::MatrixX3i tris(nTris, 3);
321
322 constexpr float kTwoPi = 2.0f * 3.14159265358979f;
323
324 // Bottom ring vertices [0..sides-1]
325 for (int i = 0; i < sides; ++i) {
326 const float angle = kTwoPi * static_cast<float>(i) / static_cast<float>(sides);
327 const float ca = std::cos(angle);
328 const float sa = std::sin(angle);
329 const QVector3D normal = (u * ca + v * sa);
330 const QVector3D pos = from + normal * radius;
331 verts(i, 0) = pos.x();
332 verts(i, 1) = pos.y();
333 verts(i, 2) = pos.z();
334 norms(i, 0) = normal.x();
335 norms(i, 1) = normal.y();
336 norms(i, 2) = normal.z();
337 }
338
339 // Top ring vertices [sides..2*sides-1]
340 for (int i = 0; i < sides; ++i) {
341 const float angle = kTwoPi * static_cast<float>(i) / static_cast<float>(sides);
342 const float ca = std::cos(angle);
343 const float sa = std::sin(angle);
344 const QVector3D normal = (u * ca + v * sa);
345 const QVector3D pos = to + normal * radius;
346 verts(sides + i, 0) = pos.x();
347 verts(sides + i, 1) = pos.y();
348 verts(sides + i, 2) = pos.z();
349 norms(sides + i, 0) = normal.x();
350 norms(sides + i, 1) = normal.y();
351 norms(sides + i, 2) = normal.z();
352 }
353
354 // Cap center vertices
355 const int bottomCenter = 2 * sides;
356 const int topCenter = 2 * sides + 1;
357 verts(bottomCenter, 0) = from.x(); verts(bottomCenter, 1) = from.y(); verts(bottomCenter, 2) = from.z();
358 norms(bottomCenter, 0) = -axisN.x(); norms(bottomCenter, 1) = -axisN.y(); norms(bottomCenter, 2) = -axisN.z();
359 verts(topCenter, 0) = to.x(); verts(topCenter, 1) = to.y(); verts(topCenter, 2) = to.z();
360 norms(topCenter, 0) = axisN.x(); norms(topCenter, 1) = axisN.y(); norms(topCenter, 2) = axisN.z();
361
362 // Barrel triangles (2 tris per side)
363 int t = 0;
364 for (int i = 0; i < sides; ++i) {
365 const int i0 = i;
366 const int i1 = (i + 1) % sides;
367 const int i2 = sides + i;
368 const int i3 = sides + (i + 1) % sides;
369 tris(t, 0) = i0; tris(t, 1) = i2; tris(t, 2) = i1; ++t;
370 tris(t, 0) = i1; tris(t, 1) = i2; tris(t, 2) = i3; ++t;
371 }
372
373 // Bottom cap
374 for (int i = 0; i < sides; ++i) {
375 tris(t, 0) = bottomCenter;
376 tris(t, 1) = (i + 1) % sides;
377 tris(t, 2) = i;
378 ++t;
379 }
380
381 // Top cap
382 for (int i = 0; i < sides; ++i) {
383 tris(t, 0) = topCenter;
384 tris(t, 1) = sides + i;
385 tris(t, 2) = sides + (i + 1) % sides;
386 ++t;
387 }
388
389 auto surf = std::make_shared<BrainSurface>();
390 surf->createFromData(verts, norms, tris, color);
391 return surf;
392}
393
394//=============================================================================================================
395
396std::shared_ptr<BrainSurface> MeshFactory::createBatchedSpheres(const QVector<QVector3D> &positions,
397 float radius,
398 const QColor &color,
399 int subdivisions)
400{
401 if (positions.isEmpty())
402 return std::make_shared<BrainSurface>();
403
404 // Build template sphere once
405 QVector<Eigen::Vector3f> tmplVerts;
406 QVector<Eigen::Vector3i> tmplFaces;
407 buildIcosphere(tmplVerts, tmplFaces, subdivisions);
408
409 const int nVPerPt = tmplVerts.size();
410 const int nTPerPt = tmplFaces.size();
411 const int nPts = positions.size();
412
413 // Scale template
414 Eigen::MatrixX3f templateV(nVPerPt, 3), templateN(nVPerPt, 3);
415 for (int i = 0; i < nVPerPt; ++i) {
416 templateN(i, 0) = tmplVerts[i].x();
417 templateN(i, 1) = tmplVerts[i].y();
418 templateN(i, 2) = tmplVerts[i].z();
419 templateV(i, 0) = tmplVerts[i].x() * radius;
420 templateV(i, 1) = tmplVerts[i].y() * radius;
421 templateV(i, 2) = tmplVerts[i].z() * radius;
422 }
423 Eigen::MatrixX3i templateT(nTPerPt, 3);
424 for (int i = 0; i < nTPerPt; ++i)
425 templateT.row(i) = tmplFaces[i];
426
427 // Merge all spheres into single mesh
428 Eigen::MatrixX3f allVerts(nPts * nVPerPt, 3);
429 Eigen::MatrixX3f allNorms(nPts * nVPerPt, 3);
430 Eigen::MatrixX3i allTris(nPts * nTPerPt, 3);
431
432 for (int p = 0; p < nPts; ++p) {
433 const QVector3D &pos = positions[p];
434 const int vOff = p * nVPerPt;
435 const int tOff = p * nTPerPt;
436 for (int iv = 0; iv < nVPerPt; ++iv) {
437 allVerts(vOff + iv, 0) = templateV(iv, 0) + pos.x();
438 allVerts(vOff + iv, 1) = templateV(iv, 1) + pos.y();
439 allVerts(vOff + iv, 2) = templateV(iv, 2) + pos.z();
440 allNorms.row(vOff + iv) = templateN.row(iv);
441 }
442 for (int it = 0; it < nTPerPt; ++it) {
443 allTris(tOff + it, 0) = templateT(it, 0) + vOff;
444 allTris(tOff + it, 1) = templateT(it, 1) + vOff;
445 allTris(tOff + it, 2) = templateT(it, 2) + vOff;
446 }
447 }
448
449 auto surf = std::make_shared<BrainSurface>();
450 surf->createFromData(allVerts, allNorms, allTris, color);
451 return surf;
452}
Renderable cortical / BEM mesh with interleaved vertex attributes and Qt-RHI buffer management.
Static factory for procedural Qt-RHI meshes (spheres, plates, barbells, cylinders,...
static std::shared_ptr< BrainSurface > createPlate(const QVector3D &center, const QMatrix4x4 &orientation, const QColor &color, float size)
static std::shared_ptr< BrainSurface > createBatchedSpheres(const QVector< QVector3D > &positions, float radius, const QColor &color, int subdivisions=1)
static std::shared_ptr< BrainSurface > createBarbell(const QVector3D &center, const QMatrix4x4 &orientation, const QColor &color, float size)
static int sphereVertexCount(int subdivisions=1)
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 > createSphere(const QVector3D &center, float radius, const QColor &color, int subdivisions=1)