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