| 1 | // Copyright (C) 2020 The Qt Company Ltd. |
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
| 3 | |
| 4 | #include "qssgmeshbvhbuilder_p.h" |
| 5 | #include <QtQuick3DUtils/private/qssgassert_p.h> |
| 6 | |
| 7 | QT_BEGIN_NAMESPACE |
| 8 | |
| 9 | static constexpr quint32 QSSG_MAX_TREE_DEPTH = 40; |
| 10 | static constexpr quint32 QSSG_MAX_LEAF_TRIANGLES = 10; |
| 11 | |
| 12 | QSSGMeshBVHBuilder::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(s1: entry.m_name, s2: QSSGMesh::MeshInternal::getPositionAttrName())) { |
| 31 | m_hasPositionData = true; |
| 32 | m_vertexPosOffset = entry.m_firstItemOffset; |
| 33 | } else if (!strcmp(s1: entry.m_name, s2: QSSGMesh::MeshInternal::getUV0AttrName())) { |
| 34 | m_hasUVData = true; |
| 35 | m_vertexUVOffset = entry.m_firstItemOffset; |
| 36 | } else if (!m_hasUVData && !strcmp(s1: entry.m_name, s2: QSSGMesh::MeshInternal::getUV1AttrName())) { |
| 37 | m_hasUVData = true; |
| 38 | m_vertexUVOffset = entry.m_firstItemOffset; |
| 39 | } |
| 40 | } |
| 41 | m_vertexStride = vb.stride; |
| 42 | } |
| 43 | |
| 44 | QSSGMeshBVHBuilder::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 | |
| 68 | std::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(type: m_indexBufferComponentType)); |
| 82 | else |
| 83 | indexCount = m_vertexBufferData.size() / m_vertexStride; |
| 84 | triangleBounds = calculateTriangleBounds(indexOffset: 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(n: 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(bvh: *meshBvh, offset: triangleOffset, count: triangleCount); |
| 98 | // Recursively split the mesh into a tree of smaller bounding volumns |
| 99 | root = splitNode(bvh&: *meshBvh, node: root, offset: triangleOffset, count: triangleCount); |
| 100 | roots.push_back(x: root); |
| 101 | } |
| 102 | } else { |
| 103 | // Custom Geometry only has one subset |
| 104 | QSSGMeshBVHNode::Handle root = meshBvh->newHandle(); |
| 105 | root->boundingData = getBounds(bvh: *meshBvh, offset: 0, count: quint32(triangleBounds.size())); |
| 106 | root = splitNode(bvh&: *meshBvh, node: root, offset: 0, count: quint32(triangleBounds.size())); |
| 107 | roots.push_back(x: root); |
| 108 | } |
| 109 | |
| 110 | return meshBvh; |
| 111 | } |
| 112 | |
| 113 | |
| 114 | template <QSSGRenderComponentType ComponentType> |
| 115 | static 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 | |
| 132 | static inline QVector3D getVertexBufferValuePosition(quint32 index, const quint32 vertexStride, const quint32 vertexPosOffset, const QByteArray &vertexBufferData) |
| 133 | { |
| 134 | const quint32 offset = index * vertexStride + vertexPosOffset; |
| 135 | const QVector3D *position = reinterpret_cast<const QVector3D *>(vertexBufferData.begin() + offset); |
| 136 | |
| 137 | return *position; |
| 138 | } |
| 139 | |
| 140 | static inline QVector2D getVertexBufferValueUV(quint32 index, const quint32 vertexStride, const quint32 vertexUVOffset, const QByteArray &vertexBufferData) |
| 141 | { |
| 142 | const quint32 offset = index * vertexStride + vertexUVOffset; |
| 143 | const QVector2D *uv = reinterpret_cast<const QVector2D *>(vertexBufferData.begin() + offset); |
| 144 | |
| 145 | return *uv; |
| 146 | } |
| 147 | |
| 148 | template <QSSGRenderComponentType ComponentType, bool hasIndexBuffer, bool hasPositionData, bool hasUVData> |
| 149 | static void calculateTriangleBoundsImpl(quint32 indexOffset, |
| 150 | quint32 indexCount, |
| 151 | const QByteArray &indexBufferData, |
| 152 | const QByteArray &vertexBufferData, |
| 153 | [[maybe_unused]] const quint32 vertexStride, |
| 154 | [[maybe_unused]] const quint32 vertexUVOffset, |
| 155 | [[maybe_unused]] const quint32 vertexPosOffset, |
| 156 | QSSGMeshBVHTriangles &triangleBounds) |
| 157 | { |
| 158 | const quint32 triangleCount = indexCount / 3; |
| 159 | triangleBounds.reserve(n: triangleCount); |
| 160 | |
| 161 | for (quint32 i = 0; i < triangleCount; ++i) { |
| 162 | QSSGMeshBVHTriangle triangle{}; |
| 163 | if constexpr (hasIndexBuffer || hasPositionData || hasUVData) { |
| 164 | // Get the indices for the triangle |
| 165 | const quint32 triangleIndex = i * 3 + indexOffset; |
| 166 | |
| 167 | quint32 index1 = triangleIndex + 0; |
| 168 | quint32 index2 = triangleIndex + 1; |
| 169 | quint32 index3 = triangleIndex + 2; |
| 170 | |
| 171 | if constexpr (hasIndexBuffer) { |
| 172 | index1 = getIndexBufferValue<ComponentType>(index1, indexCount, indexBufferData); |
| 173 | index2 = getIndexBufferValue<ComponentType>(index2, indexCount, indexBufferData); |
| 174 | index3 = getIndexBufferValue<ComponentType>(index3, indexCount, indexBufferData); |
| 175 | } |
| 176 | |
| 177 | if constexpr (hasPositionData) { |
| 178 | triangle.vertex1 = getVertexBufferValuePosition(index: index1, vertexStride, vertexPosOffset, vertexBufferData); |
| 179 | triangle.vertex2 = getVertexBufferValuePosition(index: index2, vertexStride, vertexPosOffset, vertexBufferData); |
| 180 | triangle.vertex3 = getVertexBufferValuePosition(index: index3, vertexStride, vertexPosOffset, vertexBufferData); |
| 181 | } |
| 182 | |
| 183 | if constexpr (hasUVData) { |
| 184 | triangle.uvCoord1 = getVertexBufferValueUV(index: index1, vertexStride, vertexUVOffset, vertexBufferData); |
| 185 | triangle.uvCoord2 = getVertexBufferValueUV(index: index2, vertexStride, vertexUVOffset, vertexBufferData); |
| 186 | triangle.uvCoord3 = getVertexBufferValueUV(index: index3, vertexStride, vertexUVOffset, vertexBufferData); |
| 187 | } |
| 188 | } |
| 189 | |
| 190 | triangle.bounds.include(v: triangle.vertex1); |
| 191 | triangle.bounds.include(v: triangle.vertex2); |
| 192 | triangle.bounds.include(v: triangle.vertex3); |
| 193 | triangleBounds.push_back(x: triangle); |
| 194 | } |
| 195 | } |
| 196 | |
| 197 | QSSGMeshBVHTriangles QSSGMeshBVHBuilder::calculateTriangleBounds(quint32 indexOffset, quint32 indexCount) const |
| 198 | { |
| 199 | QSSGMeshBVHTriangles data; |
| 200 | |
| 201 | using CalcTriangleBoundsFn = void (*)(quint32, quint32, const QByteArray &, const QByteArray &, const quint32, const quint32, const quint32, QSSGMeshBVHTriangles &); |
| 202 | static const CalcTriangleBoundsFn calcTriangleBounds16Fns[] { &calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt16, false, false, false>, |
| 203 | &calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt16, false, false, true>, |
| 204 | &calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt16, false, true, false>, |
| 205 | &calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt16, false, true, true>, |
| 206 | &calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt16, true, false, false>, |
| 207 | &calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt16, true, false, true>, |
| 208 | &calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt16, true, true, false>, |
| 209 | &calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt16, true, true, true> }; |
| 210 | |
| 211 | static const CalcTriangleBoundsFn calcTriangleBounds32Fns[] { &calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt32, false, false, false>, |
| 212 | &calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt32, false, false, true>, |
| 213 | &calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt32, false, true, false>, |
| 214 | &calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt32, false, true, true>, |
| 215 | &calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt32, true, false, false>, |
| 216 | &calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt32, true, false, true>, |
| 217 | &calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt32, true, true, false>, |
| 218 | &calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt32, true, true, true> }; |
| 219 | |
| 220 | |
| 221 | const size_t idx = (size_t(m_hasIndexBuffer) << 2u) | (size_t(m_hasPositionData) << 1u) | (size_t(m_hasUVData)); |
| 222 | |
| 223 | if (m_indexBufferComponentType == QSSGRenderComponentType::UnsignedInt16) |
| 224 | calcTriangleBounds16Fns[idx](indexOffset, indexCount, m_indexBufferData, m_vertexBufferData, m_vertexStride, m_vertexUVOffset, m_vertexPosOffset, data); |
| 225 | else if (m_indexBufferComponentType == QSSGRenderComponentType::UnsignedInt32) |
| 226 | calcTriangleBounds32Fns[idx](indexOffset, indexCount, m_indexBufferData, m_vertexBufferData, m_vertexStride, m_vertexUVOffset, m_vertexPosOffset, data); |
| 227 | return data; |
| 228 | } |
| 229 | |
| 230 | QSSGMeshBVHNode::Handle QSSGMeshBVHBuilder::splitNode(QSSGMeshBVH &bvh, QSSGMeshBVHNode::Handle node, quint32 offset, quint32 count, quint32 depth) |
| 231 | { |
| 232 | // NOTE: The node handle argument is intentionally copied! We can risk the storage reallocating! |
| 233 | // Besides, it's a trivial type. |
| 234 | |
| 235 | // Force a leaf node if the there are too few triangles or the tree depth |
| 236 | // has exceeded the maximum depth |
| 237 | if (count < QSSG_MAX_LEAF_TRIANGLES || depth >= QSSG_MAX_TREE_DEPTH) { |
| 238 | node->offset = offset; |
| 239 | node->count = count; |
| 240 | return node; |
| 241 | } |
| 242 | |
| 243 | // Determine where to split the current bounds |
| 244 | const QSSGMeshBVHBuilder::Split split = getOptimalSplit(bvh, nodeBounds: node->boundingData, offset, count); |
| 245 | // Really this shouldn't happen unless there is invalid bounding data, but if that |
| 246 | // that does happen make this a leaf node. |
| 247 | if (split.axis == QSSGMeshBVHBuilder::Axis::None) { |
| 248 | node->offset = offset; |
| 249 | node->count = count; |
| 250 | return node; |
| 251 | } |
| 252 | |
| 253 | // Create the split by sorting the values in m_triangleBounds between |
| 254 | // offset - count based on the split axis and position. The returned offset |
| 255 | // will determine which values go into the left and right nodes. |
| 256 | const quint32 splitOffset = partition(bvh, offset, count, split); |
| 257 | |
| 258 | // Create the leaf nodes |
| 259 | if (splitOffset == offset || splitOffset == (offset + count)) { |
| 260 | // If the split is at the start or end, this is a leaf node now |
| 261 | // because there is no further branches necessary. |
| 262 | node->offset = offset; |
| 263 | node->count = count; |
| 264 | } else { |
| 265 | // Create the Left Node |
| 266 | node->left = bvh.newHandle(); |
| 267 | const quint32 leftOffset = offset; |
| 268 | const quint32 leftCount = splitOffset - offset; |
| 269 | node->left->boundingData = getBounds(bvh, offset: leftOffset, count: leftCount); |
| 270 | node->left = splitNode(bvh, node: node->left, offset: leftOffset, count: leftCount, depth: depth + 1); |
| 271 | |
| 272 | // Create the Right Node |
| 273 | node->right = bvh.newHandle(); |
| 274 | const quint32 rightOffset = splitOffset; |
| 275 | const quint32 rightCount = count - leftCount; |
| 276 | node->right->boundingData = getBounds(bvh, offset: rightOffset, count: rightCount); |
| 277 | node->right = splitNode(bvh, node: node->right, offset: rightOffset, count: rightCount, depth: depth + 1); |
| 278 | } |
| 279 | |
| 280 | return node; |
| 281 | } |
| 282 | |
| 283 | QSSGBounds3 QSSGMeshBVHBuilder::getBounds(const QSSGMeshBVH &bvh, quint32 offset, quint32 count) |
| 284 | { |
| 285 | QSSGBounds3 totalBounds; |
| 286 | const auto &triangleBounds = bvh.triangles(); |
| 287 | |
| 288 | for (quint32 i = 0; i < count; ++i) { |
| 289 | const QSSGBounds3 &bounds = triangleBounds[i + offset].bounds; |
| 290 | totalBounds.include(b: bounds); |
| 291 | } |
| 292 | return totalBounds; |
| 293 | } |
| 294 | |
| 295 | QSSGMeshBVHBuilder::Split QSSGMeshBVHBuilder::getOptimalSplit(const QSSGMeshBVH &bvh, const QSSGBounds3 &nodeBounds, quint32 offset, quint32 count) |
| 296 | { |
| 297 | QSSGMeshBVHBuilder::Split split; |
| 298 | split.axis = getLongestDimension(nodeBounds); |
| 299 | split.pos = 0.f; |
| 300 | |
| 301 | if (split.axis != Axis::None) |
| 302 | split.pos = getAverageValue(bvh, offset, count, axis: split.axis); |
| 303 | |
| 304 | return split; |
| 305 | } |
| 306 | |
| 307 | QSSGMeshBVHBuilder::Axis QSSGMeshBVHBuilder::getLongestDimension(const QSSGBounds3 &nodeBounds) |
| 308 | { |
| 309 | QSSGMeshBVHBuilder::Axis axis = Axis::None; |
| 310 | float largestDistance = std::numeric_limits<float>::min(); |
| 311 | |
| 312 | if (!nodeBounds.isFinite() || nodeBounds.isEmpty()) |
| 313 | return axis; |
| 314 | |
| 315 | const QVector3D delta = nodeBounds.maximum - nodeBounds.minimum; |
| 316 | |
| 317 | if (delta.x() > largestDistance) { |
| 318 | axis = Axis::X; |
| 319 | largestDistance = delta.x(); |
| 320 | } |
| 321 | if (delta.y() > largestDistance) { |
| 322 | axis = Axis::Y; |
| 323 | largestDistance = delta.y(); |
| 324 | } |
| 325 | if (delta.z() > largestDistance) |
| 326 | axis = Axis::Z; |
| 327 | |
| 328 | return axis; |
| 329 | } |
| 330 | |
| 331 | // Get the average values of triangles for a given axis |
| 332 | float QSSGMeshBVHBuilder::getAverageValue(const QSSGMeshBVH &bvh, quint32 offset, quint32 count, QSSGMeshBVHBuilder::Axis axis) |
| 333 | { |
| 334 | float average = 0; |
| 335 | |
| 336 | Q_ASSERT(axis != Axis::None); |
| 337 | Q_ASSERT(count != 0); |
| 338 | |
| 339 | const auto &triangleBounds = bvh.triangles(); |
| 340 | for (quint32 i = 0; i < count; ++i) |
| 341 | average += triangleBounds[i + offset].bounds.center(axis: int(axis)); |
| 342 | |
| 343 | return average / count; |
| 344 | } |
| 345 | |
| 346 | quint32 QSSGMeshBVHBuilder::partition(QSSGMeshBVH &bvh, quint32 offset, quint32 count, const QSSGMeshBVHBuilder::Split &split) |
| 347 | { |
| 348 | int left = offset; |
| 349 | int right = offset + count - 1; |
| 350 | const float pos = split.pos; |
| 351 | const int axis = int(split.axis); |
| 352 | |
| 353 | auto &triangleBounds = bvh.m_triangles; |
| 354 | while (true) { |
| 355 | while (left <= right && triangleBounds[left].bounds.center(axis) < pos) |
| 356 | left++; |
| 357 | |
| 358 | while (left <= right && triangleBounds[right].bounds.center(axis) >= pos) |
| 359 | right--; |
| 360 | |
| 361 | if (left < right) { |
| 362 | // Swap triangleBounds at left and right |
| 363 | std::swap(a&: triangleBounds[left], b&: triangleBounds[right]); |
| 364 | left++; |
| 365 | right--; |
| 366 | } else { |
| 367 | return left; |
| 368 | } |
| 369 | } |
| 370 | Q_UNREACHABLE(); |
| 371 | } |
| 372 | |
| 373 | QT_END_NAMESPACE |
| 374 | |