v2.0.0
Loading...
Searching...
No Matches
dipoleobject.cpp
Go to the documentation of this file.
1//=============================================================================================================
12
13//=============================================================================================================
14// INCLUDES
15//=============================================================================================================
16
17#include "dipoleobject.h"
18
19#include <cmath>
20#include <QQuaternion>
21#include <QRandomGenerator>
22
23namespace DISP3DLIB
24{
25
26//=============================================================================================================
27// PIMPL
28//=============================================================================================================
29
31
33
35{
36 return {{m_vertexData.constData(), quint32(m_vertexData.size()), m_geometryGeneration},
37 {m_indexData.constData(), quint32(m_indexData.size()), m_geometryGeneration},
38 {m_instanceData.constData(), quint32(m_instanceCount * sizeof(InstanceData)), m_instanceGeneration},
39 m_indexCount,
40 m_instanceCount};
41}
42
44{
45 createGeometry();
46
47 m_instanceCount = ecdSet.size();
48 m_instanceData.resize(m_instanceCount * sizeof(InstanceData));
49 m_loadedModels.resize(m_instanceCount);
50 InstanceData* data = reinterpret_cast<InstanceData*>(m_instanceData.data());
51
52 QVector3D from(0.0f, 1.0f, 0.0f); // Cone points up Y axis
53
54 float maxMag = 0.0f;
55 for (int i = 0; i < ecdSet.size(); ++i) {
56 float mag = std::sqrt(std::pow(ecdSet[i].Q(0), 2) + std::pow(ecdSet[i].Q(1), 2) + std::pow(ecdSet[i].Q(2), 2));
57 maxMag = std::max(maxMag, mag);
58 }
59
60 for (int i = 0; i < ecdSet.size(); ++i) {
61 const auto& dip = ecdSet[i];
62
63 QVector3D pos(dip.rd(0), dip.rd(1), dip.rd(2));
64 QVector3D Q(dip.Q(0), dip.Q(1), dip.Q(2));
65 float mag = Q.length();
66
67 QVector3D to = Q.normalized();
68 // Handle case where 'to' is parallel to 'from'
69 QQuaternion rot;
70 if (QVector3D::dotProduct(from, to) > 0.99f) {
71 rot = QQuaternion();
72 } else if (QVector3D::dotProduct(from, to) < -0.99f) {
73 rot = QQuaternion::fromAxisAndAngle(1.0f, 0.0f, 0.0f, 180.0f);
74 } else {
75 rot = QQuaternion::rotationTo(from, to);
76 }
77
78 // Scale based on magnitude relative to max, with a minimum size of 20%
79 float scaleFactor = (maxMag > 0.0f) ? (0.2f + 0.8f * (mag / maxMag)) : 1.0f;
80
81 QMatrix4x4 m;
82 m.translate(pos);
83 m.rotate(rot);
84 m.scale(scaleFactor);
85 m_loadedModels[i] = m;
86
87 const float* mPtr = m.constData();
88 for (int j = 0; j < 16; ++j) {
89 data[i].model[j] = mPtr[j];
90 }
91
92 // Random Color
93 data[i].color[0] = QRandomGenerator::global()->generateDouble();
94 data[i].color[1] = QRandomGenerator::global()->generateDouble();
95 data[i].color[2] = QRandomGenerator::global()->generateDouble();
96 data[i].color[3] = 1.0f;
97
98 // Selection state
99 data[i].isSelected = 0.0f;
100 }
101
102 ++m_instanceGeneration;
103}
104
105void DipoleObject::applyTransform(const QMatrix4x4& trans)
106{
107 if (m_instanceCount == 0)
108 return;
109
110 InstanceData* data = reinterpret_cast<InstanceData*>(m_instanceData.data());
111
112 for (int i = 0; i < m_instanceCount; ++i) {
113 const QMatrix4x4 newModel = trans * m_loadedModels[i];
114 const float* newPtr = newModel.constData();
115 for (int j = 0; j < 16; ++j) {
116 data[i].model[j] = newPtr[j];
117 }
118 }
119
120 ++m_instanceGeneration;
121}
122
123bool DipoleObject::boundingBox(QVector3D& min, QVector3D& max) const
124{
125 if (m_instanceCount == 0)
126 return false;
127 const InstanceData* data = reinterpret_cast<const InstanceData*>(m_instanceData.constData());
128 for (int i = 0; i < m_instanceCount; ++i) {
129 // Column-major model matrix: the translation is elements 12..14
130 const QVector3D pos(data[i].model[12], data[i].model[13], data[i].model[14]);
131 min = i == 0 ? pos : QVector3D(std::min(min.x(), pos.x()), std::min(min.y(), pos.y()), std::min(min.z(), pos.z()));
132 max = i == 0 ? pos : QVector3D(std::max(max.x(), pos.x()), std::max(max.y(), pos.y()), std::max(max.z(), pos.z()));
133 }
134 return true;
135}
136
137void DipoleObject::createGeometry()
138{
139 if (!m_vertexData.isEmpty())
140 return;
141
142 // Create a simple cone
143 // Radius 0.001, Height 0.003
144 // 32 segments
145
146 float radius = 0.005f; // Slightly larger for visibility
147 float height = 0.01f;
148 int segments = 16;
149
150 std::vector<VertexData> vertices;
151 std::vector<uint32_t> indices;
152
153 // Tip
154 VertexData tip = {0, height, 0, 0, 1, 0};
155 vertices.push_back(tip);
156
157 // Base center
158 VertexData baseCenter = {0, 0, 0, 0, -1, 0};
159 vertices.push_back(baseCenter); // Index 1
160
161 // Rim vertices
162 for (int i = 0; i < segments; ++i) {
163 float angle = 2.0f * M_PI * i / segments;
164 float x = radius * cos(angle);
165 float z = radius * sin(angle);
166
167 // Side normal
168 // Slope vector: (cos, -h/r, sin) -> normalized
169 QVector3D n(x, radius / height, z);
170 n.normalize();
171
172 VertexData vSide = {x, 0, z, n.x(), n.y(), n.z()};
173 vertices.push_back(vSide);
174
175 VertexData vBase = {x, 0, z, 0, -1, 0};
176 vertices.push_back(vBase);
177 }
178
179 // Indices
180 for (int i = 0; i < segments; ++i) {
181 // Cone sides
182 // Tip (0), current(i), next(next)
183 // Side vertices start at firstRimIdx.
184 // Layout: Tip, BaseCenter, Side0, Base0, Side1, Base1...
185 // Wait, easier to keep separate lists or just flat
186
187 // Re-do layout for ease:
188 // 0: Tip
189 // 1: Base Center
190 // 2..2+segments-1: Side rim vertices
191 // 2+segments..2+2*segments-1: Base rim vertices
192 }
193
194 vertices.clear();
195 vertices.push_back(tip); // 0
196 vertices.push_back(baseCenter); // 1
197
198 for (int i = 0; i < segments; ++i) {
199 float angle = 2.0f * M_PI * i / segments;
200 float x = radius * cos(angle);
201 float z = radius * sin(angle);
202
203 QVector3D n(x, 0, z); // Approximate normal for side (flat shading effectively if we don't slant)
204 // Better normal: vector perpendicular to slope
205 QVector3D slope(x, -height, z);
206 QVector3D tangent(-sin(angle), 0, cos(angle));
207 QVector3D sideNormal = QVector3D::crossProduct(tangent, slope).normalized(); // Wait, slope is vector down side.
208 // Actually simple: normal y component is radius/height ratio related.
209
210 vertices.push_back({x, 0, z, sideNormal.x(), sideNormal.y(), sideNormal.z()}); // Side rim
211 }
212
213 for (int i = 0; i < segments; ++i) {
214 float angle = 2.0f * M_PI * i / segments;
215 float x = radius * cos(angle);
216 float z = radius * sin(angle);
217 vertices.push_back({x, 0, z, 0, -1, 0}); // Base rim
218 }
219
220 int sideStart = 2;
221 int baseStart = 2 + segments;
222
223 for (int i = 0; i < segments; ++i) {
224 int next = (i + 1) % segments;
225
226 // Cone face
227 indices.push_back(0);
228 indices.push_back(sideStart + next);
229 indices.push_back(sideStart + i);
230
231 // Base face
232 indices.push_back(1);
233 indices.push_back(baseStart + i);
234 indices.push_back(baseStart + next);
235 }
236
237 m_indexCount = static_cast<int>(indices.size());
238
239 m_vertexData.resize(vertices.size() * sizeof(VertexData));
240 memcpy(m_vertexData.data(), vertices.data(), m_vertexData.size());
241
242 m_indexData.resize(indices.size() * sizeof(uint32_t));
243 memcpy(m_indexData.data(), indices.data(), m_indexData.size());
244
245 ++m_geometryGeneration;
246}
247
248int DipoleObject::intersect(const QVector3D& rayOrigin, const QVector3D& rayDir, float& dist) const
249{
250 if (m_instanceCount == 0)
251 return -1;
252
253 int closestIdx = -1;
254 float closestDist = std::numeric_limits<float>::max();
255
256 const InstanceData* data = reinterpret_cast<const InstanceData*>(m_instanceData.constData());
257
258 // Geometry radius ~ 0.005 (base) to 0.01 (height).
259 // Let's use a slightly larger hit radius to make selection easier and more accurate.
260 const float baseRadius = 0.02f;
261
262 for (int i = 0; i < m_instanceCount; ++i) {
263 // Extract translation (last column)
264 QVector3D center(data[i].model[12], data[i].model[13], data[i].model[14]);
265
266 // Extract scale (length of first column) - assumes uniform scale roughly
267 QVector3D col0(data[i].model[0], data[i].model[1], data[i].model[2]);
268 float scale = col0.length();
269
270 float radius = baseRadius * scale;
271
272 // Ray-Sphere Intersection
273 QVector3D L = center - rayOrigin;
274 float tca = QVector3D::dotProduct(L, rayDir);
275
276 if (tca < 0)
277 continue; // Behind ray
278
279 float d2 = QVector3D::dotProduct(L, L) - tca * tca;
280 if (d2 > radius * radius)
281 continue; // Miss
282
283 float thc = std::sqrt(radius * radius - d2);
284 float t0 = tca - thc;
285 float t1 = tca + thc;
286
287 float t = t0;
288 if (t < 0)
289 t = t1;
290 if (t < 0)
291 continue;
292
293 if (t < closestDist) {
294 closestDist = t;
295 closestIdx = i;
296 }
297 }
298
299 if (closestIdx != -1) {
300 dist = closestDist;
301 return closestIdx;
302 }
303
304 return -1;
305}
306
307void DipoleObject::setSelected(int index, bool selected)
308{
309 if (index < 0 || index >= m_instanceCount)
310 return;
311
312 InstanceData* data = reinterpret_cast<InstanceData*>(m_instanceData.data());
313
314 data[index].isSelected = selected ? 1.0f : 0.0f;
315
316 ++m_instanceGeneration;
317}
318
319} // namespace DISP3DLIB
Instanced-arrow renderable for fitted equivalent current dipoles.
3-D brain visualisation using the Qt RHI rendering backend.
CPU streams of an instanced mesh: one shape (vertices, indices) drawn once per instance.
Geometry of one brain surface vertex: position and normal.
void load(const INVLIB::InvEcdSet &ecdSet)
void setSelected(int index, bool selected)
InstancedStreams streams() const
int intersect(const QVector3D &rayOrigin, const QVector3D &rayDir, float &dist) const
void applyTransform(const QMatrix4x4 &trans)
bool boundingBox(QVector3D &min, QVector3D &max) const
Holds a set of Electric Current Dipoles.
Definition inv_ecd_set.h:67
qint32 size() const