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
28namespace DISP3DLIB
29{
30
31//=============================================================================================================
32// DEFINE MEMBER METHODS
33//=============================================================================================================
34
35//=============================================================================================================
36
38: m_iIndexCount(0)
39, m_iInstanceCount(0)
40, m_fRadius(0.0015f)
41, m_startColor(QColor(60, 120, 255))
42, m_endColor(QColor(255, 80, 40))
43, m_bVisible(true)
44{
45 createSegmentGeometry();
46}
47
48//=============================================================================================================
49
51
52//=============================================================================================================
53
54void PolylineObject::setPoints(const QVector<Eigen::Vector3f>& vecPoints)
55{
56 // A single point has no segment, so it is treated the same as no data at
57 // all rather than drawn as a degenerate line.
58 if (vecPoints.size() < 2) {
59 clear();
60 return;
61 }
62
63 m_vecPoints = vecPoints;
64 buildInstances();
65}
66
67//=============================================================================================================
68
70{
71 m_vecPoints.clear();
72 m_instanceData.clear();
73 m_iInstanceCount = 0;
74 ++m_instanceGeneration;
75}
76
77//=============================================================================================================
78
79void PolylineObject::setRadius(float fRadius)
80{
81 m_fRadius = fRadius;
82 buildInstances();
83}
84
85//=============================================================================================================
86
87void PolylineObject::setGradient(const QColor& startColor,
88 const QColor& endColor)
89{
90 m_startColor = startColor;
91 m_endColor = endColor;
92 buildInstances();
93}
94
95//=============================================================================================================
96
97void PolylineObject::createSegmentGeometry()
98{
99 if (!m_vertexData.isEmpty()) {
100 return;
101 }
102
103 // Unit cylinder along Y, height 1 and radius 1, scaled and rotated per
104 // instance. Eight segments is enough for a thin line and keeps the vertex
105 // count low, since a long path can have thousands of instances.
106 const int iSegments = 8;
107 const float fHalfHeight = 0.5f;
108
109 std::vector<VertexData> vertices;
110 std::vector<quint32> indices;
111
112 for (int i = 0; i < iSegments; ++i) {
113 const float fAngle = 2.0f * static_cast<float>(M_PI) * static_cast<float>(i) / static_cast<float>(iSegments);
114 const float x = std::cos(fAngle);
115 const float z = std::sin(fAngle);
116
117 QVector3D normal(x, 0.0f, z);
118 normal.normalize();
119
120 vertices.push_back({x, fHalfHeight, z, normal.x(), normal.y(), normal.z()});
121 vertices.push_back({x, -fHalfHeight, z, normal.x(), normal.y(), normal.z()});
122 }
123
124 for (int i = 0; i < iSegments; ++i) {
125 const quint32 uTop = static_cast<quint32>(i * 2);
126 const quint32 uBottom = uTop + 1;
127 const quint32 uNextTop = static_cast<quint32>(((i + 1) % iSegments) * 2);
128 const quint32 uNextBottom = uNextTop + 1;
129
130 indices.push_back(uTop);
131 indices.push_back(uBottom);
132 indices.push_back(uNextTop);
133
134 indices.push_back(uNextTop);
135 indices.push_back(uBottom);
136 indices.push_back(uNextBottom);
137 }
138
139 m_vertexData = QByteArray(reinterpret_cast<const char*>(vertices.data()),
140 static_cast<qsizetype>(vertices.size() * sizeof(VertexData)));
141 m_indexData = QByteArray(reinterpret_cast<const char*>(indices.data()),
142 static_cast<qsizetype>(indices.size() * sizeof(quint32)));
143 m_iIndexCount = static_cast<int>(indices.size());
144 ++m_geometryGeneration;
145}
146
147//=============================================================================================================
148
149void PolylineObject::buildInstances()
150{
151 if (m_vecPoints.size() < 2) {
152 m_instanceData.clear();
153 m_iInstanceCount = 0;
154 return;
155 }
156
157 const int iSegmentCount = m_vecPoints.size() - 1;
158 std::vector<InstanceData> instances;
159 instances.reserve(static_cast<size_t>(iSegmentCount));
160
161 const float fLastStep = static_cast<float>(iSegmentCount - 1);
162
163 for (int i = 0; i < iSegmentCount; ++i) {
164 const Eigen::Vector3f& vecStart = m_vecPoints.at(i);
165 const Eigen::Vector3f& vecEnd = m_vecPoints.at(i + 1);
166
167 const QVector3D start(vecStart.x(), vecStart.y(), vecStart.z());
168 const QVector3D end(vecEnd.x(), vecEnd.y(), vecEnd.z());
169
170 const QVector3D direction = end - start;
171 const float fLength = direction.length();
172
173 // Two consecutive fits can land on the same position when the subject
174 // does not move. Such a segment has no direction to orient a cylinder
175 // by, so it is skipped instead of producing a NaN rotation.
176 //
177 // The threshold has to be far below anything physical rather than
178 // qFuzzyIsNull, whose tolerance for float is 1e-5. Positions are in
179 // metres, so that would discard every step under ten micrometres and
180 // punch gaps into the path of a slowly drifting subject. A picometre
181 // is small enough that only genuinely coincident points are dropped,
182 // and large enough to keep the division below well conditioned.
183 constexpr float fMinSegmentLength = 1.0e-12f;
184 if (fLength < fMinSegmentLength) {
185 continue;
186 }
187
188 QMatrix4x4 model;
189 model.translate((start + end) * 0.5f);
190 model.rotate(QQuaternion::rotationTo(QVector3D(0.0f, 1.0f, 0.0f),
191 direction / fLength));
192 model.scale(m_fRadius, fLength, m_fRadius);
193
194 InstanceData instance{};
195 memcpy(instance.model, model.constData(), 16 * sizeof(float));
196
197 // Grade from the start colour to the end colour so the direction the
198 // path was travelled in is visible without extra decoration.
199 const float fRatio = (fLastStep > 0.0f) ? (static_cast<float>(i) / fLastStep) : 0.0f;
200 instance.color[0] = static_cast<float>(m_startColor.redF() + (m_endColor.redF() - m_startColor.redF()) * fRatio);
201 instance.color[1] = static_cast<float>(m_startColor.greenF() + (m_endColor.greenF() - m_startColor.greenF()) * fRatio);
202 instance.color[2] = static_cast<float>(m_startColor.blueF() + (m_endColor.blueF() - m_startColor.blueF()) * fRatio);
203 instance.color[3] = 1.0f;
204 instance.isSelected = 0.0f;
205
206 instances.push_back(instance);
207 }
208
209 m_instanceData = QByteArray(reinterpret_cast<const char*>(instances.data()),
210 static_cast<qsizetype>(instances.size() * sizeof(InstanceData)));
211 m_iInstanceCount = static_cast<int>(instances.size());
212 ++m_instanceGeneration;
213}
214
215//=============================================================================================================
216
218{
219 return {{m_vertexData.constData(), quint32(m_vertexData.size()), m_geometryGeneration},
220 {m_indexData.constData(), quint32(m_indexData.size()), m_geometryGeneration},
221 {m_instanceData.constData(), quint32(m_iInstanceCount * sizeof(InstanceData)), m_instanceGeneration},
222 m_iIndexCount,
223 m_iInstanceCount};
224}
225
226} // namespace DISP3DLIB
PolylineObject class declaration.
3-D brain visualisation using the Qt RHI rendering backend.
CPU streams of an instanced mesh: one shape (vertices, indices) drawn once per instance.
void setRadius(float fRadius)
InstancedStreams streams() const
void setGradient(const QColor &startColor, const QColor &endColor)
void setPoints(const QVector< Eigen::Vector3f > &vecPoints)