Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qssgmeshbvhbuilder.cpp
Go to the documentation of this file.
1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5
7#include <QtQuick3DUtils/private/qssgassert_p.h>
8
10
11static constexpr quint32 QSSG_MAX_TREE_DEPTH = 40;
12static constexpr quint32 QSSG_MAX_LEAF_TRIANGLES = 10;
13
14QSSGMeshBVHBuilder::QSSGMeshBVHBuilder(const QSSGMesh::Mesh &mesh)
15 : m_mesh(mesh)
16{
17 const QSSGMesh::Mesh::VertexBuffer vb = mesh.vertexBuffer();
18 const QSSGMesh::Mesh::IndexBuffer ib = mesh.indexBuffer();
19 m_vertexBufferData = vb.data;
20 m_indexBufferData = ib.data;
21 m_indexBufferComponentType = QSSGRenderComponentType(ib.componentType);
22 if (m_indexBufferComponentType == QSSGRenderComponentType::Int16)
23 m_indexBufferComponentType = QSSGRenderComponentType::UnsignedInt16;
24 else if (m_indexBufferComponentType == QSSGRenderComponentType::Int32)
25 m_indexBufferComponentType = QSSGRenderComponentType::UnsignedInt32;
26
27 // Get VertexBuffer Information
28 // When using the texture coordinates, UV0 has priority but if the mesh has
29 // UV1 without UV0, UV1 will be used instead of UV0.
30 for (quint32 entryIdx = 0, entryEnd = vb.entries.size(); entryIdx < entryEnd; ++entryIdx) {
31 QSSGRenderVertexBufferEntry entry = vb.entries[entryIdx].toRenderVertexBufferEntry();
32 if (!strcmp(entry.m_name, QSSGMesh::MeshInternal::getPositionAttrName())) {
33 m_hasPositionData = true;
34 m_vertexPosOffset = entry.m_firstItemOffset;
35 } else if (!strcmp(entry.m_name, QSSGMesh::MeshInternal::getUV0AttrName())) {
36 m_hasUVData = true;
37 m_vertexUVOffset = entry.m_firstItemOffset;
38 } else if (!m_hasUVData && !strcmp(entry.m_name, QSSGMesh::MeshInternal::getUV1AttrName())) {
39 m_hasUVData = true;
40 m_vertexUVOffset = entry.m_firstItemOffset;
41 }
42 }
43 m_vertexStride = vb.stride;
44}
45
46QSSGMeshBVHBuilder::QSSGMeshBVHBuilder(const QByteArray &vertexBuffer,
47 int stride,
48 int posOffset,
49 bool hasUV,
50 int uvOffset,
51 bool hasIndexBuffer,
52 const QByteArray &indexBuffer,
53 QSSGRenderComponentType indexBufferType)
54{
55 m_vertexBufferData = vertexBuffer;
56 m_vertexStride = stride;
57 m_hasPositionData = true;
58 m_vertexPosOffset = posOffset;
59 m_hasUVData = hasUV;
60 m_vertexUVOffset = uvOffset;
61 m_hasIndexBuffer = hasIndexBuffer;
62 m_indexBufferData = indexBuffer;
63 m_indexBufferComponentType = indexBufferType;
64 if (m_indexBufferComponentType == QSSGRenderComponentType::Int16)
65 m_indexBufferComponentType = QSSGRenderComponentType::UnsignedInt16;
66 else if (m_indexBufferComponentType == QSSGRenderComponentType::Int32)
67 m_indexBufferComponentType = QSSGRenderComponentType::UnsignedInt32;
68}
69
70std::unique_ptr<QSSGMeshBVH> QSSGMeshBVHBuilder::buildTree()
71{
72 // This only works with triangles
73 if (m_mesh.isValid() && m_mesh.drawMode() != QSSGMesh::Mesh::DrawMode::Triangles)
74 return nullptr;
75
76 auto meshBvh = std::make_unique<QSSGMeshBVH>();
77 auto &roots = meshBvh->m_roots;
78 auto &triangleBounds = meshBvh->m_triangles;
79
80 // Calculate the bounds for each triangle in whole mesh once
81 quint32 indexCount = 0;
82 if (m_hasIndexBuffer)
83 indexCount = quint32(m_indexBufferData.size() / QSSGBaseTypeHelpers::getSizeOfType(m_indexBufferComponentType));
84 else
85 indexCount = m_vertexBufferData.size() / m_vertexStride;
86 triangleBounds = calculateTriangleBounds(0, indexCount);
87
88 // For each submesh, generate a root bvh node
89 if (m_mesh.isValid()) {
90 const QVector<QSSGMesh::Mesh::Subset> subsets = m_mesh.subsets();
91 roots.reserve(subsets.size());
92 for (quint32 subsetIdx = 0, subsetEnd = subsets.size(); subsetIdx < subsetEnd; ++subsetIdx) {
93 const QSSGMesh::Mesh::Subset &source(subsets[subsetIdx]);
94 QSSGMeshBVHNode::Handle root = meshBvh->newHandle();
95 // Offsets provided by subset are for the index buffer
96 // Convert them to work with the triangle bounds list
97 const quint32 triangleOffset = source.offset / 3;
98 const quint32 triangleCount = source.count / 3;
99 root->boundingData = getBounds(*meshBvh, triangleOffset, triangleCount);
100 // Recursively split the mesh into a tree of smaller bounding volumns
101 root = splitNode(*meshBvh, root, triangleOffset, triangleCount);
102 roots.push_back(root);
103 }
104 } else {
105 // Custom Geometry only has one subset
106 QSSGMeshBVHNode::Handle root = meshBvh->newHandle();
107 root->boundingData = getBounds(*meshBvh, 0, quint32(triangleBounds.size()));
108 root = splitNode(*meshBvh, root, 0, quint32(triangleBounds.size()));
109 roots.push_back(root);
110 }
111
112 return meshBvh;
113}
114
115
116template <QSSGRenderComponentType ComponentType>
117static inline quint32 getIndexBufferValue(quint32 index, const quint32 indexCount, const QByteArray &indexBufferData)
118{
119 Q_ASSERT(index < indexCount);
120 Q_STATIC_ASSERT(ComponentType == QSSGRenderComponentType::UnsignedInt16 || ComponentType == QSSGRenderComponentType::UnsignedInt32);
121
122 quint32 result = 0;
123 if constexpr (ComponentType == QSSGRenderComponentType::UnsignedInt16) {
124 QSSGDataView<quint16> shortIndex(reinterpret_cast<const quint16 *>(indexBufferData.begin()), indexCount);
125 result = shortIndex[index];
126 } else if (ComponentType == QSSGRenderComponentType::UnsignedInt32) {
127 QSSGDataView<quint32> longIndex(reinterpret_cast<const quint32 *>(indexBufferData.begin()), indexCount);
128 result = longIndex[index];
129 }
130
131 return result;
132}
133
134static inline QVector3D getVertexBufferValuePosition(quint32 index, const quint32 vertexStride, const quint32 vertexPosOffset, const QByteArray &vertexBufferData)
135{
136 const quint32 offset = index * vertexStride + vertexPosOffset;
137 Q_ASSERT(qsizetype(offset) < vertexBufferData.size());
138 const QVector3D *position = reinterpret_cast<const QVector3D *>(vertexBufferData.begin() + offset);
139
140 return *position;
141}
142
143static inline QVector2D getVertexBufferValueUV(quint32 index, const quint32 vertexStride, const quint32 vertexUVOffset, const QByteArray &vertexBufferData)
144{
145 const quint32 offset = index * vertexStride + vertexUVOffset;
146 Q_ASSERT(qsizetype(offset) < vertexBufferData.size());
147 const QVector2D *uv = reinterpret_cast<const QVector2D *>(vertexBufferData.begin() + offset);
148
149 return *uv;
150}
151
152template <QSSGRenderComponentType ComponentType, bool hasIndexBuffer, bool hasPositionData, bool hasUVData>
153static void calculateTriangleBoundsImpl(quint32 indexOffset,
154 quint32 indexCount,
155 const QByteArray &indexBufferData,
156 const QByteArray &vertexBufferData,
157 [[maybe_unused]] const quint32 vertexStride,
158 [[maybe_unused]] const quint32 vertexUVOffset,
159 [[maybe_unused]] const quint32 vertexPosOffset,
160 QSSGMeshBVHTriangles &triangleBounds)
161{
162 const quint32 triangleCount = indexCount / 3;
163 triangleBounds.reserve(triangleCount);
164
165 for (quint32 i = 0; i < triangleCount; ++i) {
166 QSSGMeshBVHTriangle triangle{};
167 if constexpr (hasIndexBuffer || hasPositionData || hasUVData) {
168 // Get the indices for the triangle
169 const quint32 triangleIndex = i * 3 + indexOffset;
170
171 quint32 index1 = triangleIndex + 0;
172 quint32 index2 = triangleIndex + 1;
173 quint32 index3 = triangleIndex + 2;
174
175 if constexpr (hasIndexBuffer) {
176 index1 = getIndexBufferValue<ComponentType>(index1, indexCount, indexBufferData);
177 index2 = getIndexBufferValue<ComponentType>(index2, indexCount, indexBufferData);
178 index3 = getIndexBufferValue<ComponentType>(index3, indexCount, indexBufferData);
179 }
180
181 if constexpr (hasPositionData) {
182 triangle.vertex1 = getVertexBufferValuePosition(index1, vertexStride, vertexPosOffset, vertexBufferData);
183 triangle.vertex2 = getVertexBufferValuePosition(index2, vertexStride, vertexPosOffset, vertexBufferData);
184 triangle.vertex3 = getVertexBufferValuePosition(index3, vertexStride, vertexPosOffset, vertexBufferData);
185 }
186
187 if constexpr (hasUVData) {
188 triangle.uvCoord1 = getVertexBufferValueUV(index1, vertexStride, vertexUVOffset, vertexBufferData);
189 triangle.uvCoord2 = getVertexBufferValueUV(index2, vertexStride, vertexUVOffset, vertexBufferData);
190 triangle.uvCoord3 = getVertexBufferValueUV(index3, vertexStride, vertexUVOffset, vertexBufferData);
191 }
192 }
193
194 triangle.bounds.include(triangle.vertex1);
195 triangle.bounds.include(triangle.vertex2);
196 triangle.bounds.include(triangle.vertex3);
197 triangleBounds.push_back(triangle);
198 }
199}
200
201QSSGMeshBVHTriangles QSSGMeshBVHBuilder::calculateTriangleBounds(quint32 indexOffset, quint32 indexCount) const
202{
203 QSSGMeshBVHTriangles data;
204
205 using CalcTriangleBoundsFn = void (*)(quint32, quint32, const QByteArray &, const QByteArray &, const quint32, const quint32, const quint32, QSSGMeshBVHTriangles &);
206 static const CalcTriangleBoundsFn calcTriangleBounds16Fns[] { &calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt16, false, false, false>,
207 &calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt16, false, false, true>,
208 &calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt16, false, true, false>,
209 &calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt16, false, true, true>,
210 &calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt16, true, false, false>,
211 &calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt16, true, false, true>,
212 &calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt16, true, true, false>,
213 &calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt16, true, true, true> };
214
215 static const CalcTriangleBoundsFn calcTriangleBounds32Fns[] { &calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt32, false, false, false>,
216 &calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt32, false, false, true>,
217 &calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt32, false, true, false>,
218 &calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt32, false, true, true>,
219 &calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt32, true, false, false>,
220 &calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt32, true, false, true>,
221 &calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt32, true, true, false>,
222 &calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt32, true, true, true> };
223
224
225 const size_t idx = (size_t(m_hasIndexBuffer) << 2u) | (size_t(m_hasPositionData) << 1u) | (size_t(m_hasUVData));
226
227 if (m_indexBufferComponentType == QSSGRenderComponentType::UnsignedInt16)
228 calcTriangleBounds16Fns[idx](indexOffset, indexCount, m_indexBufferData, m_vertexBufferData, m_vertexStride, m_vertexUVOffset, m_vertexPosOffset, data);
229 else if (m_indexBufferComponentType == QSSGRenderComponentType::UnsignedInt32)
230 calcTriangleBounds32Fns[idx](indexOffset, indexCount, m_indexBufferData, m_vertexBufferData, m_vertexStride, m_vertexUVOffset, m_vertexPosOffset, data);
231 return data;
232}
233
234QSSGMeshBVHNode::Handle QSSGMeshBVHBuilder::splitNode(QSSGMeshBVH &bvh, QSSGMeshBVHNode::Handle node, quint32 offset, quint32 count, quint32 depth)
235{
236 // NOTE: The node handle argument is intentionally copied! We can risk the storage reallocating!
237 // Besides, it's a trivial type.
238
239 // Force a leaf node if the there are too few triangles or the tree depth
240 // has exceeded the maximum depth
241 if (count < QSSG_MAX_LEAF_TRIANGLES || depth >= QSSG_MAX_TREE_DEPTH) {
242 node->offset = offset;
243 node->count = count;
244 return node;
245 }
246
247 // Determine where to split the current bounds
248 const QSSGMeshBVHBuilder::Split split = getOptimalSplit(bvh, node->boundingData, offset, count);
249 // Really this shouldn't happen unless there is invalid bounding data, but if that
250 // that does happen make this a leaf node.
251 if (split.axis == QSSGMeshBVHBuilder::Axis::None) {
252 node->offset = offset;
253 node->count = count;
254 return node;
255 }
256
257 // Create the split by sorting the values in m_triangleBounds between
258 // offset - count based on the split axis and position. The returned offset
259 // will determine which values go into the left and right nodes.
260 const quint32 splitOffset = partition(bvh, offset, count, split);
261
262 // Create the leaf nodes
263 if (splitOffset == offset || splitOffset == (offset + count)) {
264 // If the split is at the start or end, this is a leaf node now
265 // because there is no further branches necessary.
266 node->offset = offset;
267 node->count = count;
268 } else {
269 // Create the Left Node
270 node->left = bvh.newHandle();
271 const quint32 leftOffset = offset;
272 const quint32 leftCount = splitOffset - offset;
273 node->left->boundingData = getBounds(bvh, leftOffset, leftCount);
274 node->left = splitNode(bvh, node->left, leftOffset, leftCount, depth + 1);
275
276 // Create the Right Node
277 node->right = bvh.newHandle();
278 const quint32 rightOffset = splitOffset;
279 const quint32 rightCount = count - leftCount;
280 node->right->boundingData = getBounds(bvh, rightOffset, rightCount);
281 node->right = splitNode(bvh, node->right, rightOffset, rightCount, depth + 1);
282 }
283
284 return node;
285}
286
287QSSGBounds3 QSSGMeshBVHBuilder::getBounds(const QSSGMeshBVH &bvh, quint32 offset, quint32 count)
288{
289 QSSGBounds3 totalBounds;
290 const auto &triangleBounds = bvh.triangles();
291
292 for (quint32 i = 0; i < count; ++i) {
293 const QSSGBounds3 &bounds = triangleBounds[i + offset].bounds;
294 totalBounds.include(bounds);
295 }
296 return totalBounds;
297}
298
299QSSGMeshBVHBuilder::Split QSSGMeshBVHBuilder::getOptimalSplit(const QSSGMeshBVH &bvh, const QSSGBounds3 &nodeBounds, quint32 offset, quint32 count)
300{
301 QSSGMeshBVHBuilder::Split split;
302 split.axis = getLongestDimension(nodeBounds);
303 split.pos = 0.f;
304
305 if (split.axis != Axis::None)
306 split.pos = getAverageValue(bvh, offset, count, split.axis);
307
308 return split;
309}
310
311QSSGMeshBVHBuilder::Axis QSSGMeshBVHBuilder::getLongestDimension(const QSSGBounds3 &nodeBounds)
312{
313 QSSGMeshBVHBuilder::Axis axis = Axis::None;
314 float largestDistance = std::numeric_limits<float>::min();
315
316 if (!nodeBounds.isFinite() || nodeBounds.isEmpty())
317 return axis;
318
319 const QVector3D delta = nodeBounds.maximum - nodeBounds.minimum;
320
321 if (delta.x() > largestDistance) {
322 axis = Axis::X;
323 largestDistance = delta.x();
324 }
325 if (delta.y() > largestDistance) {
326 axis = Axis::Y;
327 largestDistance = delta.y();
328 }
329 if (delta.z() > largestDistance)
330 axis = Axis::Z;
331
332 return axis;
333}
334
335// Get the average values of triangles for a given axis
336float QSSGMeshBVHBuilder::getAverageValue(const QSSGMeshBVH &bvh, quint32 offset, quint32 count, QSSGMeshBVHBuilder::Axis axis)
337{
338 float average = 0;
339
340 Q_ASSERT(axis != Axis::None);
341 Q_ASSERT(count != 0);
342
343 const auto &triangleBounds = bvh.triangles();
344 for (quint32 i = 0; i < count; ++i)
345 average += triangleBounds[i + offset].bounds.center(int(axis));
346
347 return average / count;
348}
349
350quint32 QSSGMeshBVHBuilder::partition(QSSGMeshBVH &bvh, quint32 offset, quint32 count, const QSSGMeshBVHBuilder::Split &split)
351{
352 int left = offset;
353 int right = offset + count - 1;
354 const float pos = split.pos;
355 const int axis = int(split.axis);
356
357 auto &triangleBounds = bvh.m_triangles;
358 while (true) {
359 while (left <= right && triangleBounds[left].bounds.center(axis) < pos)
360 left++;
361
362 while (left <= right && triangleBounds[right].bounds.center(axis) >= pos)
363 right--;
364
365 if (left < right) {
366 // Swap triangleBounds at left and right
367 std::swap(triangleBounds[left], triangleBounds[right]);
368 left++;
369 right--;
370 } else {
371 return left;
372 }
373 }
374 Q_UNREACHABLE();
375}
376
377QT_END_NAMESPACE
Combined button and popup list for selecting options.
static QT_BEGIN_NAMESPACE constexpr quint32 QSSG_MAX_TREE_DEPTH
static QVector2D getVertexBufferValueUV(quint32 index, const quint32 vertexStride, const quint32 vertexUVOffset, const QByteArray &vertexBufferData)
static quint32 getIndexBufferValue(quint32 index, const quint32 indexCount, const QByteArray &indexBufferData)
static void calculateTriangleBoundsImpl(quint32 indexOffset, quint32 indexCount, const QByteArray &indexBufferData, const QByteArray &vertexBufferData, const quint32 vertexStride, const quint32 vertexUVOffset, const quint32 vertexPosOffset, QSSGMeshBVHTriangles &triangleBounds)
static constexpr quint32 QSSG_MAX_LEAF_TRIANGLES
static QVector3D getVertexBufferValuePosition(quint32 index, const quint32 vertexStride, const quint32 vertexPosOffset, const QByteArray &vertexBufferData)