Revision control

Copy as Markdown

// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include "qssgmeshbvhbuilder_p.h"
#include <QtQuick3DUtils/private/qssgassert_p.h>
QT_BEGIN_NAMESPACE
static constexpr quint32 QSSG_MAX_TREE_DEPTH = 40;
static constexpr quint32 QSSG_MAX_LEAF_TRIANGLES = 10;
QSSGMeshBVHBuilder::QSSGMeshBVHBuilder(const QSSGMesh::Mesh &mesh)
: m_mesh(mesh)
{
const QSSGMesh::Mesh::VertexBuffer vb = mesh.vertexBuffer();
const QSSGMesh::Mesh::IndexBuffer ib = mesh.indexBuffer();
m_vertexBufferData = vb.data;
m_indexBufferData = ib.data;
m_indexBufferComponentType = QSSGRenderComponentType(ib.componentType);
if (m_indexBufferComponentType == QSSGRenderComponentType::Int16)
m_indexBufferComponentType = QSSGRenderComponentType::UnsignedInt16;
else if (m_indexBufferComponentType == QSSGRenderComponentType::Int32)
m_indexBufferComponentType = QSSGRenderComponentType::UnsignedInt32;
// Get VertexBuffer Information
// When using the texture coordinates, UV0 has priority but if the mesh has
// UV1 without UV0, UV1 will be used instead of UV0.
for (quint32 entryIdx = 0, entryEnd = vb.entries.size(); entryIdx < entryEnd; ++entryIdx) {
QSSGRenderVertexBufferEntry entry = vb.entries[entryIdx].toRenderVertexBufferEntry();
if (!strcmp(entry.m_name, QSSGMesh::MeshInternal::getPositionAttrName())) {
m_hasPositionData = true;
m_vertexPosOffset = entry.m_firstItemOffset;
} else if (!strcmp(entry.m_name, QSSGMesh::MeshInternal::getUV0AttrName())) {
m_hasUVData = true;
m_vertexUVOffset = entry.m_firstItemOffset;
} else if (!m_hasUVData && !strcmp(entry.m_name, QSSGMesh::MeshInternal::getUV1AttrName())) {
m_hasUVData = true;
m_vertexUVOffset = entry.m_firstItemOffset;
}
}
m_vertexStride = vb.stride;
}
QSSGMeshBVHBuilder::QSSGMeshBVHBuilder(const QByteArray &vertexBuffer,
int stride,
int posOffset,
bool hasUV,
int uvOffset,
bool hasIndexBuffer,
const QByteArray &indexBuffer,
QSSGRenderComponentType indexBufferType)
{
m_vertexBufferData = vertexBuffer;
m_vertexStride = stride;
m_hasPositionData = true;
m_vertexPosOffset = posOffset;
m_hasUVData = hasUV;
m_vertexUVOffset = uvOffset;
m_hasIndexBuffer = hasIndexBuffer;
m_indexBufferData = indexBuffer;
m_indexBufferComponentType = indexBufferType;
if (m_indexBufferComponentType == QSSGRenderComponentType::Int16)
m_indexBufferComponentType = QSSGRenderComponentType::UnsignedInt16;
else if (m_indexBufferComponentType == QSSGRenderComponentType::Int32)
m_indexBufferComponentType = QSSGRenderComponentType::UnsignedInt32;
}
std::unique_ptr<QSSGMeshBVH> QSSGMeshBVHBuilder::buildTree()
{
// This only works with triangles
if (m_mesh.isValid() && m_mesh.drawMode() != QSSGMesh::Mesh::DrawMode::Triangles)
return nullptr;
auto meshBvh = std::make_unique<QSSGMeshBVH>();
auto &roots = meshBvh->m_roots;
auto &triangleBounds = meshBvh->m_triangles;
// Calculate the bounds for each triangle in whole mesh once
quint32 indexCount = 0;
if (m_hasIndexBuffer)
indexCount = quint32(m_indexBufferData.size() / QSSGBaseTypeHelpers::getSizeOfType(m_indexBufferComponentType));
else
indexCount = m_vertexBufferData.size() / m_vertexStride;
triangleBounds = calculateTriangleBounds(0, indexCount);
// For each submesh, generate a root bvh node
if (m_mesh.isValid()) {
const QVector<QSSGMesh::Mesh::Subset> subsets = m_mesh.subsets();
roots.reserve(subsets.size());
for (quint32 subsetIdx = 0, subsetEnd = subsets.size(); subsetIdx < subsetEnd; ++subsetIdx) {
const QSSGMesh::Mesh::Subset &source(subsets[subsetIdx]);
QSSGMeshBVHNode::Handle root = meshBvh->newHandle();
// Offsets provided by subset are for the index buffer
// Convert them to work with the triangle bounds list
const quint32 triangleOffset = source.offset / 3;
const quint32 triangleCount = source.count / 3;
root->boundingData = getBounds(*meshBvh, triangleOffset, triangleCount);
// Recursively split the mesh into a tree of smaller bounding volumns
root = splitNode(*meshBvh, root, triangleOffset, triangleCount);
roots.push_back(root);
}
} else {
// Custom Geometry only has one subset
QSSGMeshBVHNode::Handle root = meshBvh->newHandle();
root->boundingData = getBounds(*meshBvh, 0, quint32(triangleBounds.size()));
root = splitNode(*meshBvh, root, 0, quint32(triangleBounds.size()));
roots.push_back(root);
}
return meshBvh;
}
template <QSSGRenderComponentType ComponentType>
static inline quint32 getIndexBufferValue(quint32 index, const quint32 indexCount, const QByteArray &indexBufferData)
{
Q_ASSERT(index < indexCount);
Q_STATIC_ASSERT(ComponentType == QSSGRenderComponentType::UnsignedInt16 || ComponentType == QSSGRenderComponentType::UnsignedInt32);
quint32 result = 0;
if constexpr (ComponentType == QSSGRenderComponentType::UnsignedInt16) {
QSSGDataView<quint16> shortIndex(reinterpret_cast<const quint16 *>(indexBufferData.begin()), indexCount);
result = shortIndex[index];
} else if (ComponentType == QSSGRenderComponentType::UnsignedInt32) {
QSSGDataView<quint32> longIndex(reinterpret_cast<const quint32 *>(indexBufferData.begin()), indexCount);
result = longIndex[index];
}
return result;
}
static inline QVector3D getVertexBufferValuePosition(quint32 index, const quint32 vertexStride, const quint32 vertexPosOffset, const QByteArray &vertexBufferData)
{
const quint32 offset = index * vertexStride + vertexPosOffset;
Q_ASSERT(qsizetype(offset) < vertexBufferData.size());
const QVector3D *position = reinterpret_cast<const QVector3D *>(vertexBufferData.begin() + offset);
return *position;
}
static inline QVector2D getVertexBufferValueUV(quint32 index, const quint32 vertexStride, const quint32 vertexUVOffset, const QByteArray &vertexBufferData)
{
const quint32 offset = index * vertexStride + vertexUVOffset;
Q_ASSERT(qsizetype(offset) < vertexBufferData.size());
const QVector2D *uv = reinterpret_cast<const QVector2D *>(vertexBufferData.begin() + offset);
return *uv;
}
template <QSSGRenderComponentType ComponentType, bool hasIndexBuffer, bool hasPositionData, bool hasUVData>
static void calculateTriangleBoundsImpl(quint32 indexOffset,
quint32 indexCount,
const QByteArray &indexBufferData,
const QByteArray &vertexBufferData,
[[maybe_unused]] const quint32 vertexStride,
[[maybe_unused]] const quint32 vertexUVOffset,
[[maybe_unused]] const quint32 vertexPosOffset,
QSSGMeshBVHTriangles &triangleBounds)
{
const quint32 triangleCount = indexCount / 3;
triangleBounds.reserve(triangleCount);
for (quint32 i = 0; i < triangleCount; ++i) {
QSSGMeshBVHTriangle triangle{};
if constexpr (hasIndexBuffer || hasPositionData || hasUVData) {
// Get the indices for the triangle
const quint32 triangleIndex = i * 3 + indexOffset;
quint32 index1 = triangleIndex + 0;
quint32 index2 = triangleIndex + 1;
quint32 index3 = triangleIndex + 2;
if constexpr (hasIndexBuffer) {
index1 = getIndexBufferValue<ComponentType>(index1, indexCount, indexBufferData);
index2 = getIndexBufferValue<ComponentType>(index2, indexCount, indexBufferData);
index3 = getIndexBufferValue<ComponentType>(index3, indexCount, indexBufferData);
}
if constexpr (hasPositionData) {
triangle.vertex1 = getVertexBufferValuePosition(index1, vertexStride, vertexPosOffset, vertexBufferData);
triangle.vertex2 = getVertexBufferValuePosition(index2, vertexStride, vertexPosOffset, vertexBufferData);
triangle.vertex3 = getVertexBufferValuePosition(index3, vertexStride, vertexPosOffset, vertexBufferData);
}
if constexpr (hasUVData) {
triangle.uvCoord1 = getVertexBufferValueUV(index1, vertexStride, vertexUVOffset, vertexBufferData);
triangle.uvCoord2 = getVertexBufferValueUV(index2, vertexStride, vertexUVOffset, vertexBufferData);
triangle.uvCoord3 = getVertexBufferValueUV(index3, vertexStride, vertexUVOffset, vertexBufferData);
}
}
triangle.bounds.include(triangle.vertex1);
triangle.bounds.include(triangle.vertex2);
triangle.bounds.include(triangle.vertex3);
triangleBounds.push_back(triangle);
}
}
QSSGMeshBVHTriangles QSSGMeshBVHBuilder::calculateTriangleBounds(quint32 indexOffset, quint32 indexCount) const
{
QSSGMeshBVHTriangles data;
using CalcTriangleBoundsFn = void (*)(quint32, quint32, const QByteArray &, const QByteArray &, const quint32, const quint32, const quint32, QSSGMeshBVHTriangles &);
static const CalcTriangleBoundsFn calcTriangleBounds16Fns[] { &calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt16, false, false, false>,
&calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt16, false, false, true>,
&calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt16, false, true, false>,
&calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt16, false, true, true>,
&calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt16, true, false, false>,
&calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt16, true, false, true>,
&calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt16, true, true, false>,
&calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt16, true, true, true> };
static const CalcTriangleBoundsFn calcTriangleBounds32Fns[] { &calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt32, false, false, false>,
&calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt32, false, false, true>,
&calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt32, false, true, false>,
&calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt32, false, true, true>,
&calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt32, true, false, false>,
&calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt32, true, false, true>,
&calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt32, true, true, false>,
&calculateTriangleBoundsImpl<QSSGRenderComponentType::UnsignedInt32, true, true, true> };
const size_t idx = (size_t(m_hasIndexBuffer) << 2u) | (size_t(m_hasPositionData) << 1u) | (size_t(m_hasUVData));
if (m_indexBufferComponentType == QSSGRenderComponentType::UnsignedInt16)
calcTriangleBounds16Fns[idx](indexOffset, indexCount, m_indexBufferData, m_vertexBufferData, m_vertexStride, m_vertexUVOffset, m_vertexPosOffset, data);
else if (m_indexBufferComponentType == QSSGRenderComponentType::UnsignedInt32)
calcTriangleBounds32Fns[idx](indexOffset, indexCount, m_indexBufferData, m_vertexBufferData, m_vertexStride, m_vertexUVOffset, m_vertexPosOffset, data);
return data;
}
QSSGMeshBVHNode::Handle QSSGMeshBVHBuilder::splitNode(QSSGMeshBVH &bvh, QSSGMeshBVHNode::Handle node, quint32 offset, quint32 count, quint32 depth)
{
// NOTE: The node handle argument is intentionally copied! We can risk the storage reallocating!
// Besides, it's a trivial type.
// Force a leaf node if the there are too few triangles or the tree depth
// has exceeded the maximum depth
if (count < QSSG_MAX_LEAF_TRIANGLES || depth >= QSSG_MAX_TREE_DEPTH) {
node->offset = offset;
node->count = count;
return node;
}
// Determine where to split the current bounds
const QSSGMeshBVHBuilder::Split split = getOptimalSplit(bvh, node->boundingData, offset, count);
// Really this shouldn't happen unless there is invalid bounding data, but if that
// that does happen make this a leaf node.
if (split.axis == QSSGMeshBVHBuilder::Axis::None) {
node->offset = offset;
node->count = count;
return node;
}
// Create the split by sorting the values in m_triangleBounds between
// offset - count based on the split axis and position. The returned offset
// will determine which values go into the left and right nodes.
const quint32 splitOffset = partition(bvh, offset, count, split);
// Create the leaf nodes
if (splitOffset == offset || splitOffset == (offset + count)) {
// If the split is at the start or end, this is a leaf node now
// because there is no further branches necessary.
node->offset = offset;
node->count = count;
} else {
// Create the Left Node
node->left = bvh.newHandle();
const quint32 leftOffset = offset;
const quint32 leftCount = splitOffset - offset;
node->left->boundingData = getBounds(bvh, leftOffset, leftCount);
node->left = splitNode(bvh, node->left, leftOffset, leftCount, depth + 1);
// Create the Right Node
node->right = bvh.newHandle();
const quint32 rightOffset = splitOffset;
const quint32 rightCount = count - leftCount;
node->right->boundingData = getBounds(bvh, rightOffset, rightCount);
node->right = splitNode(bvh, node->right, rightOffset, rightCount, depth + 1);
}
return node;
}
QSSGBounds3 QSSGMeshBVHBuilder::getBounds(const QSSGMeshBVH &bvh, quint32 offset, quint32 count)
{
QSSGBounds3 totalBounds;
const auto &triangleBounds = bvh.triangles();
for (quint32 i = 0; i < count; ++i) {
const QSSGBounds3 &bounds = triangleBounds[i + offset].bounds;
totalBounds.include(bounds);
}
return totalBounds;
}
QSSGMeshBVHBuilder::Split QSSGMeshBVHBuilder::getOptimalSplit(const QSSGMeshBVH &bvh, const QSSGBounds3 &nodeBounds, quint32 offset, quint32 count)
{
QSSGMeshBVHBuilder::Split split;
split.axis = getLongestDimension(nodeBounds);
split.pos = 0.f;
if (split.axis != Axis::None)
split.pos = getAverageValue(bvh, offset, count, split.axis);
return split;
}
QSSGMeshBVHBuilder::Axis QSSGMeshBVHBuilder::getLongestDimension(const QSSGBounds3 &nodeBounds)
{
QSSGMeshBVHBuilder::Axis axis = Axis::None;
float largestDistance = std::numeric_limits<float>::min();
if (!nodeBounds.isFinite() || nodeBounds.isEmpty())
return axis;
const QVector3D delta = nodeBounds.maximum - nodeBounds.minimum;
if (delta.x() > largestDistance) {
axis = Axis::X;
largestDistance = delta.x();
}
if (delta.y() > largestDistance) {
axis = Axis::Y;
largestDistance = delta.y();
}
if (delta.z() > largestDistance)
axis = Axis::Z;
return axis;
}
// Get the average values of triangles for a given axis
float QSSGMeshBVHBuilder::getAverageValue(const QSSGMeshBVH &bvh, quint32 offset, quint32 count, QSSGMeshBVHBuilder::Axis axis)
{
float average = 0;
Q_ASSERT(axis != Axis::None);
Q_ASSERT(count != 0);
const auto &triangleBounds = bvh.triangles();
for (quint32 i = 0; i < count; ++i)
average += triangleBounds[i + offset].bounds.center(int(axis));
return average / count;
}
quint32 QSSGMeshBVHBuilder::partition(QSSGMeshBVH &bvh, quint32 offset, quint32 count, const QSSGMeshBVHBuilder::Split &split)
{
int left = offset;
int right = offset + count - 1;
const float pos = split.pos;
const int axis = int(split.axis);
auto &triangleBounds = bvh.m_triangles;
while (true) {
while (left <= right && triangleBounds[left].bounds.center(axis) < pos)
left++;
while (left <= right && triangleBounds[right].bounds.center(axis) >= pos)
right--;
if (left < right) {
// Swap triangleBounds at left and right
std::swap(triangleBounds[left], triangleBounds[right]);
left++;
right--;
} else {
return left;
}
}
Q_UNREACHABLE();
}
QT_END_NAMESPACE