diff options
| author | Andy Nichols <[email protected]> | 2020-03-25 17:01:03 +0100 |
|---|---|---|
| committer | Andy Nichols <[email protected]> | 2020-04-16 12:45:27 +0200 |
| commit | bc7a4c8e7175a105f320e2d42ab5c85981ecbda4 (patch) | |
| tree | e6559d9452b7b757e92189d2667a96ee659f6b45 /src | |
| parent | 7eff78db71e33d4daea23673c8d9452fbbde753f (diff) | |
Perform picking with a Bounding Volume Hierarchy
Change-Id: I49c6b6b91c527784763b5337c77dc9fc8382d9a7
Reviewed-by: Christian Strømme <[email protected]>
Diffstat (limited to 'src')
| -rw-r--r-- | src/assetimport/assetimport.pro | 6 | ||||
| -rw-r--r-- | src/assetimport/qssgmeshbvhbuilder.cpp | 306 | ||||
| -rw-r--r-- | src/assetimport/qssgmeshbvhbuilder_p.h | 101 | ||||
| -rw-r--r-- | src/runtimerender/qssgrendermesh_p.h | 14 | ||||
| -rw-r--r-- | src/runtimerender/qssgrenderray.cpp | 141 | ||||
| -rw-r--r-- | src/runtimerender/qssgrenderray_p.h | 22 | ||||
| -rw-r--r-- | src/runtimerender/rendererimpl/qssgrendererimpl.cpp | 30 | ||||
| -rw-r--r-- | src/runtimerender/resourcemanager/qssgrenderbuffermanager.cpp | 6 | ||||
| -rw-r--r-- | src/utils/qssgmeshbvh.cpp | 40 | ||||
| -rw-r--r-- | src/utils/qssgmeshbvh_p.h | 94 | ||||
| -rw-r--r-- | src/utils/utils.pro | 2 |
11 files changed, 753 insertions, 9 deletions
diff --git a/src/assetimport/assetimport.pro b/src/assetimport/assetimport.pro index 0e2c16ea..5f15c6b2 100644 --- a/src/assetimport/assetimport.pro +++ b/src/assetimport/assetimport.pro @@ -6,9 +6,12 @@ MODULE_PLUGIN_TYPES = assetimporters QT += core-private gui qml quick3drender-private quick3dutils-private SOURCES = \ + qssgmeshbvhbuilder.cpp \ qssgmeshutilities.cpp HEADERS = \ + qssgmeshbvhbuilder_p.h \ + qtquick3dassetimportglobal_p.h \ qssgmeshutilities_p.h !integrity:!android|android_app:!wasm:!cross_compile { @@ -18,12 +21,11 @@ SOURCES += \ qssgqmlutilities.cpp HEADERS += \ - qtquick3dassetimportglobal_p.h \ qssgqmlutilities_p.h \ qssgassetimporter_p.h \ qssgassetimporterfactory_p.h \ qssgassetimporterplugin_p.h \ - qssgassetimportmanager_p.h \ + qssgassetimportmanager_p.h } DEFINES += QT_BUILD_QUICK3DASSETIMPORT_LIB diff --git a/src/assetimport/qssgmeshbvhbuilder.cpp b/src/assetimport/qssgmeshbvhbuilder.cpp new file mode 100644 index 00000000..05724d57 --- /dev/null +++ b/src/assetimport/qssgmeshbvhbuilder.cpp @@ -0,0 +1,306 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Quick 3D. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qssgmeshbvhbuilder_p.h" + +QT_BEGIN_NAMESPACE + +QSSGMeshBVHBuilder::QSSGMeshBVHBuilder(QSSGMeshUtilities::Mesh *mesh) + : m_mesh(mesh) +{ + m_baseAddress = reinterpret_cast<quint8 *>(m_mesh); + m_vertexBufferData = QSSGByteView(m_mesh->m_vertexBuffer.m_data.begin(m_baseAddress), + m_mesh->m_vertexBuffer.m_data.size()); + m_indexBufferData = QSSGByteView(m_mesh->m_indexBuffer.m_data.begin(m_baseAddress), + m_mesh->m_indexBuffer.m_data.size()); + m_indexBufferComponentType = m_mesh->m_indexBuffer.m_componentType; + if (m_indexBufferComponentType == QSSGRenderComponentType::Integer16) + m_indexBufferComponentType = QSSGRenderComponentType::UnsignedInteger16; + else if (m_indexBufferComponentType == QSSGRenderComponentType::Integer32) + m_indexBufferComponentType = QSSGRenderComponentType::UnsignedInteger32; + + // Get VertexBuffer Information + const auto &entries = m_mesh->m_vertexBuffer.m_entries; + for (quint32 entryIdx = 0, entryEnd = entries.size(); entryIdx < entryEnd; ++entryIdx) { + QSSGRenderVertexBufferEntry entry = entries.index(m_baseAddress, entryIdx).toVertexBufferEntry(m_baseAddress); + if (!strcmp(entry.m_name, QSSGMeshUtilities::Mesh::getPositionAttrName())) { + m_hasPositionData = true; + m_vertexPosOffset = entry.m_firstItemOffset; + } else if (!strcmp(entry.m_name, QSSGMeshUtilities::Mesh::getUVAttrName())) { + m_hasUVData = true; + m_vertexUV0Offset = entry.m_firstItemOffset; + } + } + m_vertexStride = m_mesh->m_vertexBuffer.m_stride; + +} + +QSSGMeshBVH* QSSGMeshBVHBuilder::buildTree() +{ + m_roots.clear(); + + // This only works with triangles + if (m_mesh->m_drawMode != QSSGRenderDrawMode::Triangles) + return nullptr; + + // Calculate the bounds for each triangle in whole mesh once + const quint32 indexCount = m_indexBufferData.size() / getSizeOfType(m_indexBufferComponentType); + m_triangleBounds = calculateTriangleBounds(0, indexCount); + + // For each submesh, generate a root bvh node + for (quint32 subsetIdx = 0, subsetEnd = m_mesh->m_subsets.size(); subsetIdx < subsetEnd; ++subsetIdx) { + const QSSGMeshUtilities::MeshSubset &source(m_mesh->m_subsets.index(m_baseAddress, subsetIdx)); + QSSGMeshBVHNode *root = new QSSGMeshBVHNode(); + // Offsets provided by subset are for the index buffer + // Convert them to work with the triangle bounds list + const quint32 triangleOffset = source.m_offset / 3; + const quint32 triangleCount = source.m_count / 3; + root->boundingData = getBounds(triangleOffset, triangleCount); + // Recursively split the mesh into a tree of smaller bounding volumns + root = splitNode(root, triangleOffset, triangleCount); + m_roots.append(root); + } + + return new QSSGMeshBVH(m_roots, m_triangleBounds); +} + +QVector<QSSGMeshBVHTriangle *> QSSGMeshBVHBuilder::calculateTriangleBounds(quint32 indexOffset, quint32 indexCount) const +{ + QVector<QSSGMeshBVHTriangle *> triangleBounds; + const quint32 triangleCount = indexCount / 3; + + for (quint32 i = 0; i < triangleCount; ++i) { + // Get the indices for the triangle + const quint32 triangleIndex = i * 3 + indexOffset; + + const quint32 index1 = getIndexBufferValue(triangleIndex + 0); + const quint32 index2 = getIndexBufferValue(triangleIndex + 1); + const quint32 index3 = getIndexBufferValue(triangleIndex + 2); + + QSSGMeshBVHTriangle *triangle = new QSSGMeshBVHTriangle(); + + triangle->vertex1 = getVertexBufferValuePosition(index1); + triangle->vertex2 = getVertexBufferValuePosition(index2); + triangle->vertex3 = getVertexBufferValuePosition(index3); + triangle->uvCoord1 = getVertexBufferValueUV0(index1); + triangle->uvCoord2 = getVertexBufferValueUV0(index2); + triangle->uvCoord3 = getVertexBufferValueUV0(index3); + + triangle->bounds = QSSGBounds3::empty(); + triangle->bounds.include(triangle->vertex1); + triangle->bounds.include(triangle->vertex2); + triangle->bounds.include(triangle->vertex3); + triangleBounds.append(triangle); + } + return triangleBounds; +} + +quint32 QSSGMeshBVHBuilder::getIndexBufferValue(quint32 index) const +{ + quint32 result = 0; + const quint32 indexCount = m_indexBufferData.size() / getSizeOfType(m_indexBufferComponentType); + Q_ASSERT(index < indexCount); + + if (m_indexBufferComponentType == QSSGRenderComponentType::UnsignedInteger16) { + QSSGDataView<quint16> shortIndex(reinterpret_cast<const quint16 *>(m_indexBufferData.begin()), indexCount); + result = shortIndex[index]; + } else if (m_indexBufferComponentType == QSSGRenderComponentType::UnsignedInteger32) { + QSSGDataView<quint32> longIndex(reinterpret_cast<const quint32 *>(m_indexBufferData.begin()), indexCount); + result = longIndex[index]; + } else { + // If you get here something terrible happend + Q_ASSERT(false); + } + return result; +} + +QVector3D QSSGMeshBVHBuilder::getVertexBufferValuePosition(quint32 index) const +{ + if (!m_hasPositionData) + return QVector3D(); + + const quint32 offset = index * m_vertexStride + m_vertexPosOffset; + const QVector3D *position = reinterpret_cast<const QVector3D *>(m_vertexBufferData.begin() + offset); + + return *position; +} + +QVector2D QSSGMeshBVHBuilder::getVertexBufferValueUV0(quint32 index) const +{ + if (!m_hasUVData) + return QVector2D(); + + const quint32 offset = index * m_vertexStride + m_vertexUV0Offset; + const QVector2D *uv0 = reinterpret_cast<const QVector2D *>(m_vertexBufferData.begin() + offset); + + return *uv0; +} + +QSSGMeshBVHNode *QSSGMeshBVHBuilder::splitNode(QSSGMeshBVHNode *node, quint32 offset, quint32 count, quint32 depth) +{ + // Force a leaf node if the there are too few triangles or the tree depth + // has exceeded the maximum depth + if (count < m_maxLeafTriangles || depth >= m_maxTreeDepth) { + node->offset = offset; + node->count = count; + return node; + } + + // Determine where to split the current bounds + const QSSGMeshBVHBuilder::Split split = getOptimalSplit(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(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 = new QSSGMeshBVHNode(); + const quint32 leftOffset = offset; + const quint32 leftCount = splitOffset - offset; + node->left->boundingData = getBounds(leftOffset, leftCount); + node->left = splitNode(node->left, leftOffset, leftCount, depth + 1); + + // Create the Right Node + node->right = new QSSGMeshBVHNode(); + const quint32 rightOffset = splitOffset; + const quint32 rightCount = count - leftCount; + node->right->boundingData = getBounds(rightOffset, rightCount); + node->right = splitNode(node->right, rightOffset, rightCount, depth + 1); + } + + return node; +} + +QSSGBounds3 QSSGMeshBVHBuilder::getBounds(quint32 offset, quint32 count) const +{ + QSSGBounds3 totalBounds = QSSGBounds3::empty(); + + for (quint32 i = 0; i < count; ++i) { + QSSGBounds3 bounds = m_triangleBounds[i + offset]->bounds; + totalBounds.include(bounds); + } + return totalBounds; +} + +QSSGMeshBVHBuilder::Split QSSGMeshBVHBuilder::getOptimalSplit(const QSSGBounds3 &nodeBounds, quint32 offset, quint32 count) const +{ + QSSGMeshBVHBuilder::Split split; + split.axis = getLongestDimension(nodeBounds); + split.pos = 0.f; + + if (split.axis != Axis::None) + split.pos = getAverageValue(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; + largestDistance = delta.z(); + } + return axis; +} + +// Get the average values of triangles for a given axis +float QSSGMeshBVHBuilder::getAverageValue(quint32 offset, quint32 count, QSSGMeshBVHBuilder::Axis axis) const +{ + float average = 0; + + Q_ASSERT(axis != Axis::None); + Q_ASSERT(count != 0); + + for (quint32 i = 0; i < count; ++i) + average += m_triangleBounds[i + offset]->bounds.center(int(axis)); + + return average / count; +} + +quint32 QSSGMeshBVHBuilder::partition(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); + + while (true) { + while (left <= right && m_triangleBounds[left]->bounds.center()[axis] < pos) + left++; + + while (left <= right && m_triangleBounds[right]->bounds.center()[axis] >= pos) + right--; + + if (left < right) { + // Swap triangleBounds at left and right + auto temp = m_triangleBounds[left]; + m_triangleBounds[left] = m_triangleBounds[right]; + m_triangleBounds[right] = temp; + + left++; + right--; + } else { + return left; + } + } + Q_UNREACHABLE(); +} + +QT_END_NAMESPACE diff --git a/src/assetimport/qssgmeshbvhbuilder_p.h b/src/assetimport/qssgmeshbvhbuilder_p.h new file mode 100644 index 00000000..f797ff07 --- /dev/null +++ b/src/assetimport/qssgmeshbvhbuilder_p.h @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Quick 3D. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSSGMESHBVHBUILDER_H +#define QSSGMESHBVHBUILDER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// +#include <QtQuick3DAssetImport/private/qtquick3dassetimportglobal_p.h> +#include <QtQuick3DUtils/private/qssgmeshbvh_p.h> +#include <QtQuick3DAssetImport/private/qssgmeshutilities_p.h> + +QT_BEGIN_NAMESPACE + +class Q_QUICK3DASSETIMPORT_EXPORT QSSGMeshBVHBuilder +{ +public: + QSSGMeshBVHBuilder(QSSGMeshUtilities::Mesh *mesh); + + QSSGMeshBVH* buildTree(); + +private: + enum class Axis + { + None = -1, + X = 0, + Y = 1, + Z = 2 + }; + struct Split { + Axis axis; + float pos; + }; + + QVector<QSSGMeshBVHTriangle*> calculateTriangleBounds(quint32 indexOffset, quint32 indexCount) const; + quint32 getIndexBufferValue(quint32 index) const; + QVector3D getVertexBufferValuePosition(quint32 index) const; + QVector2D getVertexBufferValueUV0(quint32 index) const; + + QSSGMeshBVHNode *splitNode(QSSGMeshBVHNode *node, quint32 offset, quint32 count, quint32 depth = 0); + QSSGBounds3 getBounds(quint32 offset, quint32 count) const; + Split getOptimalSplit(const QSSGBounds3 &nodeBounds, quint32 offset, quint32 count) const; + static Axis getLongestDimension(const QSSGBounds3 &nodeBounds); + float getAverageValue(quint32 offset, quint32 count, Axis axis) const; + quint32 partition(quint32 offset, quint32 count, const Split &split); + + QSSGMeshUtilities::Mesh *m_mesh; + quint8 *m_baseAddress; + QSSGRenderComponentType m_indexBufferComponentType; + QSSGByteView m_indexBufferData; + QSSGByteView m_vertexBufferData; + quint32 m_vertexStride; + bool m_hasPositionData = false; + quint32 m_vertexPosOffset; + bool m_hasUVData = false; + quint32 m_vertexUV0Offset; + + + QVector<QSSGMeshBVHTriangle *> m_triangleBounds; + QVector<QSSGMeshBVHNode *> m_roots; + quint32 m_maxTreeDepth = 40; + quint32 m_maxLeafTriangles = 10; +}; + +QT_END_NAMESPACE + +#endif // QSSGMESHBVHBUILDER_H diff --git a/src/runtimerender/qssgrendermesh_p.h b/src/runtimerender/qssgrendermesh_p.h index fcbe331d..3707478d 100644 --- a/src/runtimerender/qssgrendermesh_p.h +++ b/src/runtimerender/qssgrendermesh_p.h @@ -47,6 +47,7 @@ #include <QtQuick3DRender/private/qssgrenderinputassembler_p.h> #include <QtQuick3DUtils/private/qssgbounds3_p.h> +#include <QtQuick3DUtils/private/qssgmeshbvh_p.h> QT_BEGIN_NAMESPACE @@ -55,9 +56,13 @@ struct QSSGRenderSubsetBase quint32 count; quint32 offset; QSSGBounds3 bounds; // Vertex buffer bounds + QSSGMeshBVHNode *bvhRoot = nullptr; QSSGRenderSubsetBase() = default; QSSGRenderSubsetBase(const QSSGRenderSubsetBase &inOther) - : count(inOther.count), offset(inOther.offset), bounds(inOther.bounds) + : count(inOther.count) + , offset(inOther.offset) + , bounds(inOther.bounds) + , bvhRoot(inOther.bvhRoot) { } @@ -66,6 +71,7 @@ struct QSSGRenderSubsetBase count = inOther.count; offset = inOther.offset; bounds = inOther.bounds; + bvhRoot = inOther.bvhRoot; return *this; } }; @@ -186,11 +192,17 @@ struct QSSGRenderMesh QSSGRenderDrawMode drawMode; QSSGRenderWinding winding; // counterclockwise quint32 meshId; // Id from the file of this mesh. + QSSGMeshBVH *bvh = nullptr; QSSGRenderMesh(QSSGRenderDrawMode inDrawMode, QSSGRenderWinding inWinding, quint32 inMeshId) : drawMode(inDrawMode), winding(inWinding), meshId(inMeshId) { } + + ~QSSGRenderMesh() + { + delete bvh; + } }; QT_END_NAMESPACE diff --git a/src/runtimerender/qssgrenderray.cpp b/src/runtimerender/qssgrenderray.cpp index 9aa22f74..50d8106d 100644 --- a/src/runtimerender/qssgrenderray.cpp +++ b/src/runtimerender/qssgrenderray.cpp @@ -32,6 +32,8 @@ #include <QtQuick3DUtils/private/qssgplane_p.h> #include <QtQuick3DUtils/private/qssgutils_p.h> +#include <QtQuick3DUtils/private/qssgmeshbvh_p.h> +#include <QtQuick3DRuntimeRender/private/qssgrendermesh_p.h> QT_BEGIN_NAMESPACE @@ -55,7 +57,7 @@ QSSGRenderRay::RayData QSSGRenderRay::createRayData(const QMatrix4x4 &globalTran QVector3D transformedOrigin = mat44::transform(originTransform, ray.origin); float *outOriginTransformPtr(originTransform.data()); outOriginTransformPtr[12] = outOriginTransformPtr[13] = outOriginTransformPtr[14] = 0.0f; - const QVector3D &transformedDirection = mat44::rotate(originTransform, ray.direction); + const QVector3D &transformedDirection = mat44::rotate(originTransform, ray.direction).normalized(); static auto getInverseAndDirOp = [](const QVector3D &dir, QVector3D &invDir, DirectionOp (&dirOp)[3]) { for (int i = 0; i != 3; ++i) { const float axisDir = dir[i]; @@ -130,6 +132,68 @@ QSSGRenderRay::HitResult QSSGRenderRay::intersectWithAABBv2(const QSSGRenderRay: return { tmin, tmax, &bounds }; } +bool QSSGRenderRay::triangleIntersect(const QSSGRenderRay &ray, + const QVector3D &v0, + const QVector3D &v1, + const QVector3D &v2, + float &u, + float &v) +{ + // Compute the Triangle's Normal (N) + const QVector3D v0v1 = v1 - v0; + const QVector3D v0v2 = v2 - v0; + const QVector3D normal = QVector3D::crossProduct(v0v1, v0v2); + const float denominator = QVector3D::dotProduct(normal, normal); + + // Find the Intersection point (P) + + // Check for the case where the ray and plane are parallel + const float Vd = QVector3D::dotProduct(normal, ray.direction); + if (std::abs(Vd) < 0.0001f) + return false; + + const float d = QVector3D::dotProduct(normal, v0); + + // Check if the triangle is behind the ray start + const float t = -(QVector3D::dotProduct(normal, ray.origin) - d) / Vd; + if (t < 0) + return false; + + // Get the intersetion Point (P) on Triangle Plane + const QVector3D P = ray.origin + t * ray.direction; + + // Test if P is inside of the triangle + QVector3D C; + + // Edge 0 + const QVector3D edge0 = v1 - v0; + const QVector3D vp0 = P - v0; + C = QVector3D::crossProduct(edge0, vp0); + if (QVector3D::dotProduct(normal, C) < 0) + return false; + + // Edge 1 + const QVector3D edge1 = v2 - v1; + const QVector3D vp1 = P - v1; + C = QVector3D::crossProduct(edge1, vp1); + u = QVector3D::dotProduct(normal, C); + if (u < 0) + return false; + + // Edge 2 + const QVector3D edge2 = v0 - v2; + const QVector3D vp2 = P - v2; + C = QVector3D::crossProduct(edge2, vp2); + v = QVector3D::dotProduct(normal, C); + if (v < 0) + return false; + + u /= denominator; + v /= denominator; + + return true; +} + QSSGRenderRay::IntersectionResult QSSGRenderRay::intersectWithAABB(const QMatrix4x4 &inGlobalTransform, const QSSGBounds3 &inBounds, const QSSGRenderRay &ray, @@ -206,6 +270,81 @@ QSSGRenderRay::IntersectionResult QSSGRenderRay::intersectWithAABB(const QMatrix return IntersectionResult(rayLengthSquared, relXY, newPosInGlobal); } +void QSSGRenderRay::intersectWithBVH(const RayData &data, + const QSSGMeshBVHNode *bvh, + const QSSGRenderMesh *mesh, + QVector<IntersectionResult> &intersections, + int depth) +{ + if (!bvh || !mesh || !mesh->bvh) + return; + + // If this is a leaf node, process it's triangles + if (bvh->count != 0) { + // If there is an intersection on a leaf node, then test against geometry + auto results = intersectWithBVHTriangles(data, mesh->bvh->triangles, bvh->offset, bvh->count); + if (!results.isEmpty()) + intersections.append(results); + return; + } + + auto hit = QSSGRenderRay::intersectWithAABBv2(data, bvh->left->boundingData); + if (hit.intersects()) + intersectWithBVH(data, bvh->left, mesh, intersections, depth + 1); + + hit = QSSGRenderRay::intersectWithAABBv2(data, bvh->right->boundingData); + if (hit.intersects()) + intersectWithBVH(data, bvh->right, mesh, intersections, depth + 1); +} + + + +QVector<QSSGRenderRay::IntersectionResult> QSSGRenderRay::intersectWithBVHTriangles(const RayData &data, + const QVector<QSSGMeshBVHTriangle *> &bvhTriangles, + int triangleOffset, + int triangleCount) +{ + Q_ASSERT(bvhTriangles.count() >= triangleOffset + triangleCount); + + QVector<QSSGRenderRay::IntersectionResult> results; + + for (int i = triangleOffset; i < triangleCount + triangleOffset; ++i) { + const auto &triangle = bvhTriangles[i]; + + QSSGRenderRay relativeRay(data.origin, data.direction); + + // Use Barycentric Coordinates to get the intersection values + float u = 0.f; + float v = 0.f; + const bool intersects = triangleIntersect(relativeRay, + triangle->vertex1, + triangle->vertex2, + triangle->vertex3, + u, + v); + if (intersects) { + const float w = 1.0f - u - v; + const QVector3D localIntersectionPoint = u * triangle->vertex1 + + v * triangle->vertex2 + + w * triangle->vertex3; + + const QVector2D uvCoordinate = u * triangle->uvCoord1 + + v * triangle->uvCoord2 + + w * triangle->uvCoord3; + // Get the intersection point in scene coordinates + const QVector3D sceneIntersectionPos = mat44::transform(data.globalTransform, + localIntersectionPoint); + const QVector3D hitVector = data.ray.origin - sceneIntersectionPos; + // Get the magnitude of the hit vector + const float rayLengthSquared = vec3::magnitudeSquared(hitVector); + results.append(IntersectionResult(rayLengthSquared, uvCoordinate, sceneIntersectionPos)); + } + } + + // Does not intersect with any of the triangles + return results; +} + QSSGOption<QVector2D> QSSGRenderRay::relative(const QMatrix4x4 &inGlobalTransform, const QSSGBounds3 &inBounds, QSSGRenderBasisPlanes inPlane) const diff --git a/src/runtimerender/qssgrenderray_p.h b/src/runtimerender/qssgrenderray_p.h index 0b2dacbb..5528cede 100644 --- a/src/runtimerender/qssgrenderray_p.h +++ b/src/runtimerender/qssgrenderray_p.h @@ -51,6 +51,9 @@ #include <QtGui/QMatrix4x4> QT_BEGIN_NAMESPACE +struct QSSGMeshBVHNode; +struct QSSGRenderMesh; +struct QSSGMeshBVHTriangle; enum class QSSGRenderBasisPlanes { XY, @@ -70,6 +73,14 @@ struct Q_AUTOTEST_EXPORT QSSGRenderRay // If we are parallel, then no intersection of course. static QSSGOption<QVector3D> intersect(const QSSGPlane &inPlane, const QSSGRenderRay &ray); + // Perform an intersection aslo returning Barycentric Coordinates + static bool triangleIntersect(const QSSGRenderRay &ray, + const QVector3D &v0, + const QVector3D &v1, + const QVector3D &v2, + float &u, + float &v); + struct IntersectionResult { bool intersects = false; @@ -124,6 +135,17 @@ struct Q_AUTOTEST_EXPORT QSSGRenderRay const QSSGRenderRay &ray, bool inForceIntersect = false); + static void intersectWithBVH(const RayData &data, + const QSSGMeshBVHNode *bvh, + const QSSGRenderMesh *mesh, + QVector<IntersectionResult> &intersections, + int depth = 0); + + static QVector<IntersectionResult> intersectWithBVHTriangles(const RayData &data, + const QVector<QSSGMeshBVHTriangle *> &bvhTriangles, + int triangleOffset, + int triangleCount); + QSSGOption<QVector2D> relative(const QMatrix4x4 &inGlobalTransform, const QSSGBounds3 &inBounds, QSSGRenderBasisPlanes inPlane) const; diff --git a/src/runtimerender/rendererimpl/qssgrendererimpl.cpp b/src/runtimerender/rendererimpl/qssgrendererimpl.cpp index 8aa46167..0c106510 100644 --- a/src/runtimerender/rendererimpl/qssgrendererimpl.cpp +++ b/src/runtimerender/rendererimpl/qssgrendererimpl.cpp @@ -883,6 +883,7 @@ void QSSGRendererImpl::intersectRayWithSubsetRenderable(const QSSGRef<QSSGBuffer return; const auto &globalTransform = model.globalTransform; + auto rayData = QSSGRenderRay::createRayData(globalTransform, inRay); const auto &subMeshes = mesh->subsets; QSSGBounds3 modelBounds = QSSGBounds3::empty(); for (const auto &subMesh : subMeshes) @@ -891,18 +892,37 @@ void QSSGRendererImpl::intersectRayWithSubsetRenderable(const QSSGRef<QSSGBuffer if (modelBounds.isEmpty()) return; - QSSGRenderRay::IntersectionResult intersectionResult = QSSGRenderRay::intersectWithAABB(globalTransform, modelBounds, inRay); + auto hit = QSSGRenderRay::intersectWithAABBv2(rayData, modelBounds); // If we don't intersect with the model at all, then there's no need to go furher down! - if (!intersectionResult.intersects) + if (!hit.intersects()) return; // Check each submesh to find the closest intersection point float minRayLength = std::numeric_limits<float>::max(); - // reset intersectionResult - intersectionResult = QSSGRenderRay::IntersectionResult(); + QSSGRenderRay::IntersectionResult intersectionResult; + QVector<QSSGRenderRay::IntersectionResult> results; + for (const auto &subMesh : subMeshes) { - const auto result = QSSGRenderRay::intersectWithAABB(globalTransform, subMesh.bounds, inRay); + QSSGRenderRay::IntersectionResult result; + if (subMesh.bvhRoot) { + hit = QSSGRenderRay::intersectWithAABBv2(rayData, subMesh.bvhRoot->boundingData); + if (hit.intersects()) { + results.clear(); + inRay.intersectWithBVH(rayData, subMesh.bvhRoot, mesh, results); + float subMeshMinRayLength = std::numeric_limits<float>::max(); + for (const auto &subMeshResult : qAsConst(results)) { + if (subMeshResult.rayLengthSquared < subMeshMinRayLength) { + result = subMeshResult; + subMeshMinRayLength = result.rayLengthSquared; + } + } + } + } else { + hit = QSSGRenderRay::intersectWithAABBv2(rayData, subMesh.bounds); + if (hit.intersects()) + result = QSSGRenderRay::createIntersectionResult(rayData, hit); + } if (result.intersects && result.rayLengthSquared < minRayLength) { intersectionResult = result; minRayLength = intersectionResult.rayLengthSquared; diff --git a/src/runtimerender/resourcemanager/qssgrenderbuffermanager.cpp b/src/runtimerender/resourcemanager/qssgrenderbuffermanager.cpp index 9230d8e5..0ad000a8 100644 --- a/src/runtimerender/resourcemanager/qssgrenderbuffermanager.cpp +++ b/src/runtimerender/resourcemanager/qssgrenderbuffermanager.cpp @@ -32,6 +32,7 @@ #include <QtQuick3DRuntimeRender/private/qssgrenderprefiltertexture_p.h> #include <QtQuick3DRuntimeRender/private/qssgruntimerenderlogging_p.h> +#include <QtQuick3DAssetImport/private/qssgmeshbvhbuilder_p.h> #include <QtQuick/QSGTexture> @@ -495,10 +496,15 @@ QSSGRenderMesh *QSSGBufferManager::createRenderMesh( ::memcpy(newJoint.localToGlobalBoneSpace, importJoint.m_localToGlobalBoneSpace, 16 * sizeof(float)); } + // Build BVH for Mesh + QSSGMeshBVHBuilder meshBVHBuilder(result.m_mesh); + newMesh->bvh = meshBVHBuilder.buildTree(); + for (quint32 subsetIdx = 0, subsetEnd = result.m_mesh->m_subsets.size(); subsetIdx < subsetEnd; ++subsetIdx) { QSSGRenderSubset subset; const QSSGMeshUtilities::MeshSubset &source(result.m_mesh->m_subsets.index(baseAddress, subsetIdx)); subset.bounds = source.m_bounds; + subset.bvhRoot = newMesh->bvh->roots.at(subsetIdx); subset.count = source.m_count; subset.offset = source.m_offset; subset.joints = newMesh->joints; diff --git a/src/utils/qssgmeshbvh.cpp b/src/utils/qssgmeshbvh.cpp new file mode 100644 index 00000000..0a78463d --- /dev/null +++ b/src/utils/qssgmeshbvh.cpp @@ -0,0 +1,40 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Quick 3D. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qssgmeshbvh_p.h" + +QT_BEGIN_NAMESPACE + +QSSGMeshBVH::~QSSGMeshBVH() +{ + qDeleteAll(triangles); + qDeleteAll(roots); +} + +QT_END_NAMESPACE diff --git a/src/utils/qssgmeshbvh_p.h b/src/utils/qssgmeshbvh_p.h new file mode 100644 index 00000000..6a18e4fb --- /dev/null +++ b/src/utils/qssgmeshbvh_p.h @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Quick 3D. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSSGMESHBVH_H +#define QSSGMESHBVH_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuick3DUtils/private/qtquick3dutilsglobal_p.h> +#include <QtQuick3DUtils/private/qssgbounds3_p.h> + +#include <QtGui/QVector2D> +#include <QtCore/QVector> + +QT_BEGIN_NAMESPACE + +struct Q_QUICK3DUTILS_EXPORT QSSGMeshBVHNode { + ~QSSGMeshBVHNode() { + delete left; + delete right; + } + + // Internal + QSSGMeshBVHNode *left = nullptr; + QSSGMeshBVHNode *right = nullptr; + QSSGBounds3 boundingData; + //splitAxis + + // Leaf + int offset = 0; + int count = 0; +}; + +struct Q_QUICK3DUTILS_EXPORT QSSGMeshBVHTriangle { + QSSGBounds3 bounds; + QVector3D vertex1; + QVector3D vertex2; + QVector3D vertex3; + QVector2D uvCoord1; + QVector2D uvCoord2; + QVector2D uvCoord3; +}; + +struct Q_QUICK3DUTILS_EXPORT QSSGMeshBVH +{ + QSSGMeshBVH(const QVector<QSSGMeshBVHNode *> &bvhRoots, + const QVector<QSSGMeshBVHTriangle *> &bvhTriangles) + : roots(bvhRoots) + , triangles(bvhTriangles) + {} + ~QSSGMeshBVH(); + + QVector<QSSGMeshBVHNode *> roots; + QVector<QSSGMeshBVHTriangle *> triangles; +}; + +QT_END_NAMESPACE + +#endif // QSSGMESHBVH_H diff --git a/src/utils/utils.pro b/src/utils/utils.pro index 3986bdf3..f8434b06 100644 --- a/src/utils/utils.pro +++ b/src/utils/utils.pro @@ -7,6 +7,7 @@ DEFINES += QT_BUILD_QUICK3DUTILS_LIB HEADERS += \ qssgbounds3_p.h \ + qssgmeshbvh_p.h \ qssgutils_p.h \ qssgdataref_p.h \ qssgoption_p.h \ @@ -18,6 +19,7 @@ HEADERS += \ SOURCES += \ qssgbounds3.cpp \ qssgdataref.cpp \ + qssgmeshbvh.cpp \ qssgperftimer.cpp \ qssgplane.cpp \ qssgutils.cpp |
