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
24namespace DISP3DLIB
25{
26
27//=============================================================================================================
28// STATIC HELPERS
29//=============================================================================================================
30
31namespace
32{
33
34Eigen::Matrix3f extractRotation(const QMatrix4x4& m)
35{
36 Eigen::Matrix3f rot;
37 for (int r = 0; r < 3; ++r)
38 for (int c = 0; c < 3; ++c)
39 rot(r, c) = m(r, c);
40 return rot;
41}
42
43} // anonymous namespace
44
45//=============================================================================================================
46// DEFINE MEMBER METHODS
47//=============================================================================================================
48
49void MeshFactory::buildIcosphere(QVector<Eigen::Vector3f>& vertices,
50 QVector<Eigen::Vector3i>& faces,
51 int subdivisions)
52{
53 // Golden ratio
54 const float phi = (1.0f + std::sqrt(5.0f)) / 2.0f;
55
56 vertices.clear();
57 vertices.reserve(12 + 30 * subdivisions); // rough estimate
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)
65 v.normalize();
66
67 faces.clear();
68 faces.reserve(20);
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);
79
80 // Subdivide
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))
86 return cache[key];
87 int idx = vertices.size();
88 vertices.append((vertices[a] + vertices[b]).normalized());
89 cache[key] = idx;
90 return idx;
91 };
92
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);
103 }
104 faces = std::move(newFaces);
105 }
106}
107
108//=============================================================================================================
109
111{
112 QVector<Eigen::Vector3f> verts;
113 QVector<Eigen::Vector3i> faces;
114 buildIcosphere(verts, faces, subdivisions);
115 return verts.size();
116}
117
118//=============================================================================================================
119
120std::shared_ptr<BrainSurface> MeshFactory::createSphere(const QVector3D& center,
121 float radius,
122 const QColor& color,
123 int subdivisions)
124{
125 QVector<Eigen::Vector3f> verts;
126 QVector<Eigen::Vector3i> faces;
127 buildIcosphere(verts, faces, subdivisions);
128
129 const int nV = verts.size();
130 const int nT = faces.size();
131
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();
140 }
141
142 Eigen::MatrixX3i tris(nT, 3);
143 for (int i = 0; i < nT; ++i)
144 tris.row(i) = faces[i];
145
146 auto surf = std::make_shared<BrainSurface>();
147 surf->createFromData(rr, nn, tris, color);
148 return surf;
149}
150
151//=============================================================================================================
152
153std::shared_ptr<BrainSurface> MeshFactory::createPlate(const QVector3D& center,
154 const QMatrix4x4& orientation,
155 const QColor& color,
156 float size)
157{
158 const float hw = size / 2.0f;
159 const float hh = size / 2.0f;
160 const float hd = size * 0.05f; // thin plate
161
162 const Eigen::Matrix3f rot = extractRotation(orientation);
163
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)
167 c = rot * c;
168
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}};
171
172 Eigen::MatrixX3f rr(24, 3), nn(24, 3);
173 Eigen::MatrixX3i tris(12, 3);
174
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();
182 int base = f * 4;
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;
187 }
188 tris.row(f * 2) << base, base + 1, base + 2;
189 tris.row(f * 2 + 1) << base, base + 2, base + 3;
190 }
191
192 auto surf = std::make_shared<BrainSurface>();
193 surf->createFromData(rr, nn, tris, color);
194 return surf;
195}
196
197//=============================================================================================================
198
199std::shared_ptr<BrainSurface> MeshFactory::createBarbell(const QVector3D& center,
200 const QMatrix4x4& orientation,
201 const QColor& color,
202 float size)
203{
204 const Eigen::Matrix3f rot = extractRotation(orientation);
205
206 const float halfSpan = size * 0.6f;
207 const float sphereR = size * 0.2f;
208 const float rodR = size * 0.08f;
209
210 QVector<Eigen::Vector3f> allVerts;
211 QVector<Eigen::Vector3f> allNorms;
212 QVector<Eigen::Vector3i> allTris;
213
214 // ── Helper: append a sphere ────────────────────────────────────────
215 auto appendSphere = [&](const QVector3D& pos, float radius) {
216 QVector<Eigen::Vector3f> sv;
217 QVector<Eigen::Vector3i> sf;
218 buildIcosphere(sv, sf, 1); // single subdivision
219
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());
224
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());
230 }
231 for (const auto& f : sf) {
232 allTris.append(Eigen::Vector3i(base + f(0), base + f(1), base + f(2)));
233 }
234 };
235
236 // ── Helper: append a box-rod ───────────────────────────────────────
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)
242 c = rot * c;
243
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}};
246
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()));
259 allNorms.append(fn);
260 }
261 allTris.append(Eigen::Vector3i(faceBase, faceBase + 1, faceBase + 2));
262 allTris.append(Eigen::Vector3i(faceBase, faceBase + 2, faceBase + 3));
263 }
264 };
265
266 // Build the barbell
267 QVector3D axis(rot(0, 1), rot(1, 1), rot(2, 1));
268 appendSphere(center + axis * halfSpan, sphereR);
269 appendSphere(center - axis * halfSpan, sphereR);
270 appendRod();
271
272 // Pack into Eigen matrices
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();
278 }
279 for (int i = 0; i < allTris.size(); ++i)
280 tt.row(i) = allTris[i];
281
282 auto surf = std::make_shared<BrainSurface>();
283 surf->createFromData(rr, nn, tt, color);
284 return surf;
285}
286
287//=============================================================================================================
288
289std::shared_ptr<BrainSurface> MeshFactory::createCylinder(const QVector3D& from,
290 const QVector3D& to,
291 float radius,
292 const QColor& color,
293 int sides)
294{
295 if (sides < 3)
296 sides = 3;
297
298 const QVector3D axis = to - from;
299 const float length = axis.length();
300 if (length < 1e-8f)
301 return std::make_shared<BrainSurface>();
302
303 const QVector3D axisN = axis / length;
304
305 // Build an orthonormal basis (axisN, u, v)
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));
309 u.normalize();
310 const QVector3D v = QVector3D::crossProduct(axisN, u).normalized();
311
312 // Two rings of vertices (bottom = from, top = to) + 2 center caps
313 const int nVerts = 2 * sides + 2;
314 const int nTris = 4 * sides; // 2 * sides for barrel + sides for each cap
315
316 Eigen::MatrixX3f verts(nVerts, 3);
317 Eigen::MatrixX3f norms(nVerts, 3);
318 Eigen::MatrixX3i tris(nTris, 3);
319
320 constexpr float kTwoPi = 2.0f * 3.14159265358979f;
321
322 // Bottom ring vertices [0..sides-1]
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();
335 }
336
337 // Top ring vertices [sides..2*sides-1]
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();
350 }
351
352 // Cap center vertices
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();
367
368 // Barrel triangles (2 tris per side)
369 int t = 0;
370 for (int i = 0; i < sides; ++i) {
371 const int i0 = i;
372 const int i1 = (i + 1) % sides;
373 const int i2 = sides + i;
374 const int i3 = sides + (i + 1) % sides;
375 tris(t, 0) = i0;
376 tris(t, 1) = i2;
377 tris(t, 2) = i1;
378 ++t;
379 tris(t, 0) = i1;
380 tris(t, 1) = i2;
381 tris(t, 2) = i3;
382 ++t;
383 }
384
385 // Bottom cap
386 for (int i = 0; i < sides; ++i) {
387 tris(t, 0) = bottomCenter;
388 tris(t, 1) = (i + 1) % sides;
389 tris(t, 2) = i;
390 ++t;
391 }
392
393 // Top cap
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;
398 ++t;
399 }
400
401 auto surf = std::make_shared<BrainSurface>();
402 surf->createFromData(verts, norms, tris, color);
403 return surf;
404}
405
406//=============================================================================================================
407
408std::shared_ptr<BrainSurface> MeshFactory::createBatchedSpheres(const QVector<QVector3D>& positions,
409 float radius,
410 const QColor& color,
411 int subdivisions)
412{
413 if (positions.isEmpty())
414 return std::make_shared<BrainSurface>();
415
416 // Build template sphere once
417 QVector<Eigen::Vector3f> tmplVerts;
418 QVector<Eigen::Vector3i> tmplFaces;
419 buildIcosphere(tmplVerts, tmplFaces, subdivisions);
420
421 const int nVPerPt = tmplVerts.size();
422 const int nTPerPt = tmplFaces.size();
423 const int nPts = positions.size();
424
425 // Scale template
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;
434 }
435 Eigen::MatrixX3i templateT(nTPerPt, 3);
436 for (int i = 0; i < nTPerPt; ++i)
437 templateT.row(i) = tmplFaces[i];
438
439 // Merge all spheres into single mesh
440 Eigen::MatrixX3f allVerts(nPts * nVPerPt, 3);
441 Eigen::MatrixX3f allNorms(nPts * nVPerPt, 3);
442 Eigen::MatrixX3i allTris(nPts * nTPerPt, 3);
443
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);
453 }
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;
458 }
459 }
460
461 auto surf = std::make_shared<BrainSurface>();
462 surf->createFromData(allVerts, allNorms, allTris, color);
463 return surf;
464}
465
466} // namespace DISP3DLIB
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 &center, 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 &center, float radius, const QColor &color, int subdivisions=1)
static int sphereVertexCount(int subdivisions=1)
static std::shared_ptr< BrainSurface > createPlate(const QVector3D &center, const QMatrix4x4 &orientation, const QColor &color, float size)