v2.0.0
Loading...
Searching...
No Matches
polylineobject.cpp
Go to the documentation of this file.
1//=============================================================================================================
12
13//=============================================================================================================
14// INCLUDES
15//=============================================================================================================
16
17#include "polylineobject.h"
18
19//=============================================================================================================
20// QT INCLUDES
21//=============================================================================================================
22
23#include <QMatrix4x4>
24#include <QQuaternion>
25#include <QVector3D>
26#include <QtMath>
27#include <rhi/qrhi.h>
28
29namespace DISP3DLIB
30{
31
32//=============================================================================================================
33// DEFINE MEMBER METHODS
34//=============================================================================================================
35
37{
38 std::unique_ptr<QRhiBuffer> vertexBuffer;
39 std::unique_ptr<QRhiBuffer> indexBuffer;
40 std::unique_ptr<QRhiBuffer> instanceBuffer;
41};
42
43//=============================================================================================================
44
46: m_iIndexCount(0)
47, m_iInstanceCount(0)
48, m_fRadius(0.0015f)
49, m_startColor(QColor(60, 120, 255))
50, m_endColor(QColor(255, 80, 40))
51, m_bVisible(true)
52, m_bGeometryDirty(true)
53, m_bInstancesDirty(false)
54, m_gpu(std::make_unique<GpuResources>())
55{
56 createSegmentGeometry();
57}
58
59//=============================================================================================================
60
62
63//=============================================================================================================
64
65void PolylineObject::setPoints(const QVector<Eigen::Vector3f>& vecPoints)
66{
67 // A single point has no segment, so it is treated the same as no data at
68 // all rather than drawn as a degenerate line.
69 if (vecPoints.size() < 2) {
70 clear();
71 return;
72 }
73
74 m_vecPoints = vecPoints;
75 buildInstances();
76}
77
78//=============================================================================================================
79
81{
82 m_vecPoints.clear();
83 m_instanceData.clear();
84 m_iInstanceCount = 0;
85 m_bInstancesDirty = false;
86}
87
88//=============================================================================================================
89
90void PolylineObject::setRadius(float fRadius)
91{
92 m_fRadius = fRadius;
93 buildInstances();
94}
95
96//=============================================================================================================
97
98void PolylineObject::setGradient(const QColor& startColor,
99 const QColor& endColor)
100{
101 m_startColor = startColor;
102 m_endColor = endColor;
103 buildInstances();
104}
105
106//=============================================================================================================
107
108void PolylineObject::createSegmentGeometry()
109{
110 if (!m_vertexData.isEmpty()) {
111 return;
112 }
113
114 // Unit cylinder along Y, height 1 and radius 1, scaled and rotated per
115 // instance. Eight segments is enough for a thin line and keeps the vertex
116 // count low, since a long path can have thousands of instances.
117 const int iSegments = 8;
118 const float fHalfHeight = 0.5f;
119
120 std::vector<VertexData> vertices;
121 std::vector<quint32> indices;
122
123 for (int i = 0; i < iSegments; ++i) {
124 const float fAngle = 2.0f * static_cast<float>(M_PI) * static_cast<float>(i) / static_cast<float>(iSegments);
125 const float x = std::cos(fAngle);
126 const float z = std::sin(fAngle);
127
128 QVector3D normal(x, 0.0f, z);
129 normal.normalize();
130
131 vertices.push_back({x, fHalfHeight, z, normal.x(), normal.y(), normal.z()});
132 vertices.push_back({x, -fHalfHeight, z, normal.x(), normal.y(), normal.z()});
133 }
134
135 for (int i = 0; i < iSegments; ++i) {
136 const quint32 uTop = static_cast<quint32>(i * 2);
137 const quint32 uBottom = uTop + 1;
138 const quint32 uNextTop = static_cast<quint32>(((i + 1) % iSegments) * 2);
139 const quint32 uNextBottom = uNextTop + 1;
140
141 indices.push_back(uTop);
142 indices.push_back(uBottom);
143 indices.push_back(uNextTop);
144
145 indices.push_back(uNextTop);
146 indices.push_back(uBottom);
147 indices.push_back(uNextBottom);
148 }
149
150 m_vertexData = QByteArray(reinterpret_cast<const char*>(vertices.data()),
151 static_cast<qsizetype>(vertices.size() * sizeof(VertexData)));
152 m_indexData = QByteArray(reinterpret_cast<const char*>(indices.data()),
153 static_cast<qsizetype>(indices.size() * sizeof(quint32)));
154 m_iIndexCount = static_cast<int>(indices.size());
155 m_bGeometryDirty = true;
156}
157
158//=============================================================================================================
159
160void PolylineObject::buildInstances()
161{
162 if (m_vecPoints.size() < 2) {
163 m_instanceData.clear();
164 m_iInstanceCount = 0;
165 return;
166 }
167
168 const int iSegmentCount = m_vecPoints.size() - 1;
169 std::vector<InstanceData> instances;
170 instances.reserve(static_cast<size_t>(iSegmentCount));
171
172 const float fLastStep = static_cast<float>(iSegmentCount - 1);
173
174 for (int i = 0; i < iSegmentCount; ++i) {
175 const Eigen::Vector3f& vecStart = m_vecPoints.at(i);
176 const Eigen::Vector3f& vecEnd = m_vecPoints.at(i + 1);
177
178 const QVector3D start(vecStart.x(), vecStart.y(), vecStart.z());
179 const QVector3D end(vecEnd.x(), vecEnd.y(), vecEnd.z());
180
181 const QVector3D direction = end - start;
182 const float fLength = direction.length();
183
184 // Two consecutive fits can land on the same position when the subject
185 // does not move. Such a segment has no direction to orient a cylinder
186 // by, so it is skipped instead of producing a NaN rotation.
187 //
188 // The threshold has to be far below anything physical rather than
189 // qFuzzyIsNull, whose tolerance for float is 1e-5. Positions are in
190 // metres, so that would discard every step under ten micrometres and
191 // punch gaps into the path of a slowly drifting subject. A picometre
192 // is small enough that only genuinely coincident points are dropped,
193 // and large enough to keep the division below well conditioned.
194 constexpr float fMinSegmentLength = 1.0e-12f;
195 if (fLength < fMinSegmentLength) {
196 continue;
197 }
198
199 QMatrix4x4 model;
200 model.translate((start + end) * 0.5f);
201 model.rotate(QQuaternion::rotationTo(QVector3D(0.0f, 1.0f, 0.0f),
202 direction / fLength));
203 model.scale(m_fRadius, fLength, m_fRadius);
204
205 InstanceData instance{};
206 memcpy(instance.model, model.constData(), 16 * sizeof(float));
207
208 // Grade from the start colour to the end colour so the direction the
209 // path was travelled in is visible without extra decoration.
210 const float fRatio = (fLastStep > 0.0f) ? (static_cast<float>(i) / fLastStep) : 0.0f;
211 instance.color[0] = static_cast<float>(m_startColor.redF() + (m_endColor.redF() - m_startColor.redF()) * fRatio);
212 instance.color[1] = static_cast<float>(m_startColor.greenF() + (m_endColor.greenF() - m_startColor.greenF()) * fRatio);
213 instance.color[2] = static_cast<float>(m_startColor.blueF() + (m_endColor.blueF() - m_startColor.blueF()) * fRatio);
214 instance.color[3] = 1.0f;
215 instance.isSelected = 0.0f;
216
217 instances.push_back(instance);
218 }
219
220 m_instanceData = QByteArray(reinterpret_cast<const char*>(instances.data()),
221 static_cast<qsizetype>(instances.size() * sizeof(InstanceData)));
222 m_iInstanceCount = static_cast<int>(instances.size());
223 m_bInstancesDirty = m_iInstanceCount > 0;
224}
225
226//=============================================================================================================
227
228void PolylineObject::updateBuffers(QRhi* rhi, QRhiResourceUpdateBatch* u)
229{
230 if (m_bGeometryDirty) {
231 if (!m_gpu->vertexBuffer) {
232 m_gpu->vertexBuffer.reset(rhi->newBuffer(QRhiBuffer::Immutable,
233 QRhiBuffer::VertexBuffer,
234 m_vertexData.size()));
235 m_gpu->vertexBuffer->create();
236 }
237 if (!m_gpu->indexBuffer) {
238 m_gpu->indexBuffer.reset(rhi->newBuffer(QRhiBuffer::Immutable,
239 QRhiBuffer::IndexBuffer,
240 m_indexData.size()));
241 m_gpu->indexBuffer->create();
242 }
243
244 u->uploadStaticBuffer(m_gpu->vertexBuffer.get(), m_vertexData.constData());
245 u->uploadStaticBuffer(m_gpu->indexBuffer.get(), m_indexData.constData());
246 m_bGeometryDirty = false;
247 }
248
249 if (m_bInstancesDirty && m_iInstanceCount > 0) {
250 const int iRequiredSize = m_instanceData.size();
251
252 if (!m_gpu->instanceBuffer || m_gpu->instanceBuffer->size() < static_cast<quint32>(iRequiredSize)) {
253 m_gpu->instanceBuffer.reset(rhi->newBuffer(QRhiBuffer::Dynamic,
254 QRhiBuffer::VertexBuffer,
255 iRequiredSize));
256 m_gpu->instanceBuffer->create();
257 }
258
259 u->updateDynamicBuffer(m_gpu->instanceBuffer.get(), 0, iRequiredSize, m_instanceData.constData());
260 m_bInstancesDirty = false;
261 }
262}
263
264//=============================================================================================================
265
267{
268 return m_gpu->vertexBuffer.get();
269}
270
271//=============================================================================================================
272
274{
275 return m_gpu->indexBuffer.get();
276}
277
278//=============================================================================================================
279
281{
282 return m_gpu->instanceBuffer.get();
283}
284
285} // namespace DISP3DLIB
#define M_PI
PolylineObject class declaration.
3-D brain visualisation using the Qt RHI rendering backend.
std::unique_ptr< QRhiBuffer > indexBuffer
std::unique_ptr< QRhiBuffer > vertexBuffer
std::unique_ptr< QRhiBuffer > instanceBuffer
void updateBuffers(QRhi *rhi, QRhiResourceUpdateBatch *u)
QRhiBuffer * instanceBuffer() const
QRhiBuffer * indexBuffer() const
void setRadius(float fRadius)
void setGradient(const QColor &startColor, const QColor &endColor)
QRhiBuffer * vertexBuffer() const
void setPoints(const QVector< Eigen::Vector3f > &vecPoints)