Revision control
Copy as Markdown
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include "qcacheutils_p.h"
#include "qmeshshape_p.h"
#include "qphysicsutils_p.h"
#include <QtQuick3D/QQuick3DGeometry>
#include <extensions/PxExtensionsAPI.h>
#include "foundation/PxVec3.h"
#include "cooking/PxConvexMeshDesc.h"
#include "cooking/PxCooking.h"
#include "extensions/PxDefaultStreams.h"
#include <QtQml/qqml.h>
#include <QtQml/qqmlcontext.h>
#include <QtQuick3DUtils/private/qssgmesh_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrenderbuffermanager_p.h>
#include <QtQuick3D/QQuick3DGeometry>
#include "qmeshshape_p.h"
#include "qphysicsworld_p.h"
#include "qphysicsmeshutils_p_p.h"
QT_BEGIN_NAMESPACE
static bool attributeBySemantic(const QQuick3DGeometry *geometry,
QQuick3DGeometry::Attribute::Semantic semantic,
QQuick3DGeometry::Attribute *result)
{
for (int i = 0; i < geometry->attributeCount(); i++) {
const auto attr = geometry->attribute(i);
if (attr.semantic == semantic) {
*result = attr;
return true;
}
}
return false;
};
static bool isValidPositionLayout(int offset, int stride, qsizetype bufferSize)
{
const qsizetype positionSize = qsizetype(3 * sizeof(float));
if (stride <= 0 || offset < 0 || offset + positionSize > stride || bufferSize < stride) {
qWarning() << "QQuick3DPhysicsMesh: Invalid geometry, position attribute (offset"
<< offset << ") does not fit within stride" << stride << "and buffer size"
<< bufferSize;
return false;
}
return true;
}
physx::PxConvexMesh *QQuick3DPhysicsMesh::convexMesh()
{
if (m_convexMesh != nullptr)
return m_convexMesh;
physx::PxPhysics *thePhysics = QPhysicsWorld::getPhysics();
if (thePhysics == nullptr)
return nullptr;
if (m_meshGeometry)
return convexMeshGeometrySource();
if (!m_meshPath.isEmpty())
return convexMeshQmlSource();
return nullptr;
}
physx::PxTriangleMesh *QQuick3DPhysicsMesh::triangleMesh()
{
if (m_triangleMesh != nullptr)
return m_triangleMesh;
physx::PxPhysics *thePhysics = QPhysicsWorld::getPhysics();
if (thePhysics == nullptr)
return nullptr;
if (m_meshGeometry)
return triangleMeshGeometrySource();
if (!m_meshPath.isEmpty())
return triangleMeshQmlSource();
return nullptr;
}
physx::PxConvexMesh *QQuick3DPhysicsMesh::convexMeshQmlSource()
{
physx::PxPhysics *thePhysics = QPhysicsWorld::getPhysics();
m_convexMesh = QCacheUtils::readCachedConvexMesh(m_meshPath, *thePhysics);
if (m_convexMesh != nullptr)
return m_convexMesh;
m_convexMesh = QCacheUtils::readCookedConvexMesh(m_meshPath, *thePhysics);
if (m_convexMesh != nullptr)
return m_convexMesh;
loadSsgMesh();
if (!m_ssgMesh.isValid())
return nullptr;
const int vStride = m_ssgMesh.vertexBuffer().stride;
if (!isValidPositionLayout(m_posOffset, vStride, m_ssgMesh.vertexBuffer().data.size()))
return nullptr;
const int vCount = m_ssgMesh.vertexBuffer().data.size() / vStride;
qCDebug(lcQuick3dPhysics) << "prepare cooking" << vCount << "verts";
physx::PxConvexMeshDesc convexDesc;
convexDesc.points.count = vCount;
convexDesc.points.stride = vStride;
convexDesc.points.data = m_ssgMesh.vertexBuffer().data.constData() + m_posOffset;
convexDesc.flags = physx::PxConvexFlag::eCOMPUTE_CONVEX;
// NOTE: Since we are making a mesh for the convex hull and are only
// interested in the positions we can Skip the index array.
physx::PxDefaultMemoryOutputStream buf;
physx::PxConvexMeshCookingResult::Enum result;
const auto *cookingParams = QPhysicsWorld::getCookingParams();
if (cookingParams && ::PxCookConvexMesh(*cookingParams, convexDesc, buf, &result)) {
auto size = buf.getSize();
auto *data = buf.getData();
physx::PxDefaultMemoryInputData input(data, size);
m_convexMesh = thePhysics->createConvexMesh(input);
qCDebug(lcQuick3dPhysics) << "Created convex mesh" << m_convexMesh << "for mesh" << this;
QCacheUtils::writeCachedConvexMesh(m_meshPath, buf);
} else {
qCWarning(lcQuick3dPhysics) << "Could not create convex mesh from" << m_meshPath;
}
return m_convexMesh;
}
physx::PxConvexMesh *QQuick3DPhysicsMesh::convexMeshGeometrySource()
{
auto vertexBuffer = m_meshGeometry->vertexData();
if (m_meshGeometry->primitiveType() != QQuick3DGeometry::PrimitiveType::Triangles) {
qWarning() << "QQuick3DPhysicsMesh: Invalid geometry primitive type, must be Triangles. ";
return nullptr;
}
if (!vertexBuffer.size()) {
qWarning() << "QQuick3DPhysicsMesh: Invalid geometry, vertexData is empty. ";
return nullptr;
}
QQuick3DGeometry::Attribute vertexAttribute;
if (!attributeBySemantic(m_meshGeometry, QQuick3DGeometry::Attribute::PositionSemantic,
&vertexAttribute)) {
qWarning() << "QQuick3DPhysicsMesh: Invalid geometry, no position attribute found.";
return nullptr;
}
Q_ASSERT(vertexAttribute.componentType == QQuick3DGeometry::Attribute::F32Type);
const auto stride = m_meshGeometry->stride();
if (!isValidPositionLayout(vertexAttribute.offset, stride, vertexBuffer.size()))
return nullptr;
const auto numVertices = vertexBuffer.size() / stride;
physx::PxConvexMeshDesc convexDesc;
convexDesc.points.count = numVertices;
convexDesc.points.stride = stride;
convexDesc.points.data = vertexBuffer.constData() + vertexAttribute.offset;
convexDesc.flags = physx::PxConvexFlag::eCOMPUTE_CONVEX;
// NOTE: Since we are making a mesh for the convex hull and are only
// interested in the positions we can Skip the index array.
const auto *cookingParams = QPhysicsWorld::getCookingParams();
physx::PxDefaultMemoryOutputStream buf;
physx::PxConvexMeshCookingResult::Enum result;
if (cookingParams && ::PxCookConvexMesh(*cookingParams, convexDesc, buf, &result)) {
auto size = buf.getSize();
auto *data = buf.getData();
physx::PxDefaultMemoryInputData input(data, size);
m_convexMesh = QPhysicsWorld::getPhysics()->createConvexMesh(input);
qCDebug(lcQuick3dPhysics) << "Created convex mesh" << m_convexMesh << "for mesh" << this;
} else {
qCWarning(lcQuick3dPhysics) << "Could not create convex mesh for" << this;
}
return m_convexMesh;
}
physx::PxTriangleMesh *QQuick3DPhysicsMesh::triangleMeshQmlSource()
{
physx::PxPhysics *thePhysics = QPhysicsWorld::getPhysics();
m_triangleMesh = QCacheUtils::readCachedTriangleMesh(m_meshPath, *thePhysics);
if (m_triangleMesh != nullptr)
return m_triangleMesh;
m_triangleMesh = QCacheUtils::readCookedTriangleMesh(m_meshPath, *thePhysics);
if (m_triangleMesh != nullptr)
return m_triangleMesh;
loadSsgMesh();
if (!m_ssgMesh.isValid())
return nullptr;
auto vertexBuffer = m_ssgMesh.vertexBuffer().data;
const int posOffset = m_posOffset;
const auto stride = m_ssgMesh.vertexBuffer().stride;
if (!isValidPositionLayout(posOffset, stride, vertexBuffer.size()))
return nullptr;
const auto numVertices = vertexBuffer.size() / stride;
physx::PxTriangleMeshDesc triangleDesc;
triangleDesc.points.count = numVertices;
triangleDesc.points.stride = stride;
triangleDesc.points.data = vertexBuffer.constData() + posOffset;
auto indexBuffer = m_ssgMesh.indexBuffer().data;
if (indexBuffer.size()) {
const bool u16IndexType =
m_ssgMesh.indexBuffer().componentType == QSSGMesh::Mesh::ComponentType::UnsignedInt16;
Q_ASSERT(m_ssgMesh.indexBuffer().componentType
== QSSGMesh::Mesh::ComponentType::UnsignedInt16
|| m_ssgMesh.indexBuffer().componentType
== QSSGMesh::Mesh::ComponentType::UnsignedInt32);
triangleDesc.triangles.data = indexBuffer.constData();
if (u16IndexType) {
triangleDesc.flags |= physx::PxMeshFlag::e16_BIT_INDICES;
triangleDesc.triangles.stride = sizeof(quint16) * 3;
} else {
triangleDesc.triangles.stride = sizeof(quint32) * 3;
}
triangleDesc.triangles.count = indexBuffer.size() / triangleDesc.triangles.stride;
}
physx::PxDefaultMemoryOutputStream buf;
physx::PxTriangleMeshCookingResult::Enum result;
const auto *cookingParams = QPhysicsWorld::getCookingParams();
if (cookingParams && ::PxCookTriangleMesh(*cookingParams, triangleDesc, buf, &result)) {
auto size = buf.getSize();
auto *data = buf.getData();
physx::PxDefaultMemoryInputData input(data, size);
m_triangleMesh = thePhysics->createTriangleMesh(input);
qCDebug(lcQuick3dPhysics) << "Created triangle mesh" << m_triangleMesh << "for mesh"
<< this;
QCacheUtils::writeCachedTriangleMesh(m_meshPath, buf);
} else {
qCWarning(lcQuick3dPhysics) << "Could not create triangle mesh from" << m_meshPath;
}
return m_triangleMesh;
}
physx::PxTriangleMesh *QQuick3DPhysicsMesh::triangleMeshGeometrySource()
{
auto vertexBuffer = m_meshGeometry->vertexData();
if (m_meshGeometry->primitiveType() != QQuick3DGeometry::PrimitiveType::Triangles) {
qWarning() << "QQuick3DPhysicsMesh: Invalid geometry primitive type, must be Triangles. ";
return nullptr;
}
if (!vertexBuffer.size()) {
qWarning() << "QQuick3DPhysicsMesh: Invalid geometry, vertexData is empty. ";
return nullptr;
}
QQuick3DGeometry::Attribute vertexAttribute;
if (!attributeBySemantic(m_meshGeometry, QQuick3DGeometry::Attribute::PositionSemantic,
&vertexAttribute)) {
qWarning() << "QQuick3DPhysicsMesh: Invalid geometry, no position attribute found.";
return nullptr;
}
Q_ASSERT(vertexAttribute.componentType == QQuick3DGeometry::Attribute::F32Type);
const int posOffset = vertexAttribute.offset;
const auto stride = m_meshGeometry->stride();
if (!isValidPositionLayout(posOffset, stride, vertexBuffer.size()))
return nullptr;
const auto numVertices = vertexBuffer.size() / stride;
physx::PxTriangleMeshDesc triangleDesc;
triangleDesc.points.count = numVertices;
triangleDesc.points.stride = stride;
triangleDesc.points.data = vertexBuffer.constData() + posOffset;
auto indexBuffer = m_meshGeometry->indexData();
if (indexBuffer.size()) {
QQuick3DGeometry::Attribute indexAttribute;
if (!attributeBySemantic(m_meshGeometry, QQuick3DGeometry::Attribute::IndexSemantic,
&indexAttribute)) {
qWarning() << "QQuick3DPhysicsMesh: Invalid geometry, index data present but no "
"index attribute found.";
return nullptr;
}
const bool u16IndexType =
indexAttribute.componentType == QQuick3DGeometry::Attribute::U16Type;
Q_ASSERT(indexAttribute.componentType == QQuick3DGeometry::Attribute::U16Type
|| indexAttribute.componentType == QQuick3DGeometry::Attribute::U32Type);
triangleDesc.triangles.data = indexBuffer.constData();
if (u16IndexType) {
triangleDesc.flags |= physx::PxMeshFlag::e16_BIT_INDICES;
triangleDesc.triangles.stride = sizeof(quint16) * 3;
} else {
triangleDesc.triangles.stride = sizeof(quint32) * 3;
}
triangleDesc.triangles.count = indexBuffer.size() / triangleDesc.triangles.stride;
}
physx::PxDefaultMemoryOutputStream buf;
physx::PxTriangleMeshCookingResult::Enum result;
const auto *cookingParams = QPhysicsWorld::getCookingParams();
if (cookingParams && ::PxCookTriangleMesh(*cookingParams, triangleDesc, buf, &result)) {
auto size = buf.getSize();
auto *data = buf.getData();
physx::PxDefaultMemoryInputData input(data, size);
m_triangleMesh = QPhysicsWorld::getPhysics()->createTriangleMesh(input);
qCDebug(lcQuick3dPhysics) << "Created triangle mesh" << m_triangleMesh << "for mesh"
<< this;
} else {
qCWarning(lcQuick3dPhysics) << "Could not create triangle mesh for" << this;
}
return m_triangleMesh;
}
void QQuick3DPhysicsMesh::loadSsgMesh()
{
if (m_ssgMesh.isValid())
return;
// A bit ugly to use QSSGRenderPath here but it is just a wrapper for
// a QString and its hash.
// Security note: m_meshPath is a user provided file but QSSGBufferManager::loadMeshData is
// assumed to handle invalid meshes
m_ssgMesh = QSSGBufferManager::loadMeshData(QSSGRenderPath(m_meshPath));
static const char *compTypes[] = { "Null", "UnsignedInt8", "Int8", "UnsignedInt16",
"Int16", "UnsignedInt32", "Int32", "UnsignedInt64",
"Int64", "Float16", "Float32", "Float64" };
qCDebug(lcQuick3dPhysics) << "Loaded SSG mesh from" << m_meshPath << m_ssgMesh.isValid()
<< "draw" << int(m_ssgMesh.drawMode()) << "wind"
<< int(m_ssgMesh.winding()) << "subs" << m_ssgMesh.subsets().count()
<< "attrs" << m_ssgMesh.vertexBuffer().entries.count()
<< m_ssgMesh.vertexBuffer().data.size() << "stride"
<< m_ssgMesh.vertexBuffer().stride << "verts"
<< m_ssgMesh.vertexBuffer().data.size()
/ m_ssgMesh.vertexBuffer().stride;
for (auto &v : m_ssgMesh.vertexBuffer().entries) {
qCDebug(lcQuick3dPhysics) << " attr" << v.name << compTypes[int(v.componentType)] << "cc"
<< v.componentCount << "offs" << v.offset;
Q_ASSERT(v.componentType == QSSGMesh::Mesh::ComponentType::Float32);
if (v.name == "attr_pos")
m_posOffset = v.offset;
}
if (m_ssgMesh.isValid()) {
auto sub = m_ssgMesh.subsets().constFirst();
qCDebug(lcQuick3dPhysics) << "..." << sub.name << "count" << sub.count << "bounds"
<< sub.bounds.min << sub.bounds.max << "offset" << sub.offset;
}
#if 0 // EXTRA_DEBUG
int iStride = m_ssgMesh.indexBuffer().componentType == QSSGMesh::Mesh::ComponentType::UnsignedInt16 ? 2 : 4;
int vStride = m_ssgMesh.vertexBuffer().stride;
qDebug() << "IDX" << compTypes[int(m_ssgMesh.indexBuffer().componentType)] << m_ssgMesh.indexBuffer().data.size() / iStride;
const auto ib = m_ssgMesh.indexBuffer().data;
const auto vb = m_ssgMesh.vertexBuffer().data;
auto getPoint = [&vb, vStride, this](int idx) -> QVector3D {
auto *vp = vb.constData() + vStride * idx + m_posOffset;
return *reinterpret_cast<const QVector3D *>(vp);
return {};
};
if (iStride == 2) {
} else {
auto *ip = reinterpret_cast<const uint32_t *>(ib.data());
int n = ib.size() / iStride;
for (int i = 0; i < qMin(50,n); i += 3) {
qDebug() << " " << ip [i] << ip[i+1] << ip[i+2] << " --- "
<< getPoint(ip[i]) << getPoint(ip[i+1]) << getPoint(ip[i+2]);
}
}
#endif
if (!m_ssgMesh.isValid())
qCWarning(lcQuick3dPhysics) << "Could not read mesh from" << m_meshPath;
}
QQuick3DPhysicsMesh *QQuick3DPhysicsMeshManager::getMesh(const QUrl &source, QObject *contextObject)
{
const QString qmlSource = QQuick3DModel::translateMeshSource(source, contextObject);
auto *mesh = sourceMeshHash.value(qmlSource);
if (!mesh) {
mesh = new QQuick3DPhysicsMesh(qmlSource);
sourceMeshHash[qmlSource] = mesh;
}
mesh->ref();
return mesh;
}
QQuick3DPhysicsMesh *QQuick3DPhysicsMeshManager::getMesh(QQuick3DGeometry *source)
{
auto *mesh = geometryMeshHash.value(source);
if (!mesh) {
mesh = new QQuick3DPhysicsMesh(source);
geometryMeshHash.insert(source, mesh);
}
mesh->ref();
return mesh;
}
void QQuick3DPhysicsMeshManager::releaseMesh(QQuick3DPhysicsMesh *mesh)
{
if (mesh == nullptr || mesh->deref() > 0)
return;
qCDebug(lcQuick3dPhysics()) << "deleting mesh" << mesh;
erase_if(sourceMeshHash, [mesh](std::pair<const QString &, QQuick3DPhysicsMesh *&> h) {
return h.second == mesh;
});
erase_if(geometryMeshHash, [mesh](std::pair<QQuick3DGeometry *, QQuick3DPhysicsMesh *&> h) {
return h.second == mesh;
});
delete mesh;
}
QHash<QString, QQuick3DPhysicsMesh *> QQuick3DPhysicsMeshManager::sourceMeshHash;
QHash<QQuick3DGeometry *, QQuick3DPhysicsMesh *> QQuick3DPhysicsMeshManager::geometryMeshHash;
/////////////////////////////////////////////////////////////////////////////
QMeshShape::~QMeshShape()
{
delete m_convexGeometry;
if (m_mesh)
QQuick3DPhysicsMeshManager::releaseMesh(m_mesh);
}
physx::PxGeometry *QMeshShape::getPhysXGeometry()
{
if (m_dirtyPhysx || m_scaleDirty)
updatePhysXGeometry();
if (shapeType() == MeshType::CONVEX)
return m_convexGeometry;
if (shapeType() == MeshType::TRIANGLE)
return m_triangleGeometry;
Q_UNREACHABLE_RETURN(nullptr);
}
void QMeshShape::updatePhysXGeometry()
{
delete m_convexGeometry;
delete m_triangleGeometry;
m_convexGeometry = nullptr;
m_triangleGeometry = nullptr;
if (!m_mesh)
return;
auto *convexMesh = shapeType() == MeshType::CONVEX ? m_mesh->convexMesh() : nullptr;
auto *triangleMesh = shapeType() == MeshType::TRIANGLE ? m_mesh->triangleMesh() : nullptr;
if (!convexMesh && !triangleMesh)
return;
auto meshScale = sceneScale();
physx::PxMeshScale scale(physx::PxVec3(meshScale.x(), meshScale.y(), meshScale.z()),
physx::PxQuat(physx::PxIdentity));
if (!QPhysicsUtils::isFinite(meshScale)
|| !(convexMesh ? scale.isValidForConvexMesh() : scale.isValidForTriangleMesh())) {
qWarning() << "MeshShape: scaled mesh scale" << meshScale << "is invalid, ignoring.";
m_dirtyPhysx = false;
return;
}
if (convexMesh)
m_convexGeometry = new physx::PxConvexMeshGeometry(convexMesh, scale);
if (triangleMesh)
m_triangleGeometry = new physx::PxTriangleMeshGeometry(triangleMesh, scale);
m_dirtyPhysx = false;
}
const QUrl &QMeshShape::source() const
{
return m_meshSource;
}
void QMeshShape::setSource(const QUrl &newSource)
{
if (m_meshSource == newSource)
return;
m_meshSource = newSource;
// If we get a new source and our mesh was from the old source
// (meaning it was NOT from a geometry) we deref
if (m_geometry == nullptr) {
QQuick3DPhysicsMeshManager::releaseMesh(m_mesh);
m_mesh = nullptr;
}
// Load new mesh only if we don't have a geometry as source
if (m_geometry == nullptr && !newSource.isEmpty())
m_mesh = QQuick3DPhysicsMeshManager::getMesh(m_meshSource, this);
updatePhysXGeometry();
m_dirtyPhysx = true;
emit needsRebuild(this);
emit sourceChanged();
}
QQuick3DGeometry *QMeshShape::geometry() const
{
return m_geometry;
}
void QMeshShape::setGeometry(QQuick3DGeometry *newGeometry)
{
if (m_geometry == newGeometry)
return;
if (m_geometry)
m_geometry->disconnect(this);
m_geometry = newGeometry;
if (m_geometry != nullptr) {
connect(m_geometry, &QObject::destroyed, this, &QMeshShape::geometryDestroyed);
connect(m_geometry, &QQuick3DGeometry::geometryChanged, this,
&QMeshShape::geometryContentChanged);
}
// New geometry means we get a new mesh so deref the old one
QQuick3DPhysicsMeshManager::releaseMesh(m_mesh);
m_mesh = nullptr;
if (m_geometry != nullptr)
m_mesh = QQuick3DPhysicsMeshManager::getMesh(m_geometry);
else if (!m_meshSource.isEmpty())
m_mesh = QQuick3DPhysicsMeshManager::getMesh(m_meshSource, this);
updatePhysXGeometry();
m_dirtyPhysx = true;
emit needsRebuild(this);
emit geometryChanged();
}
void QMeshShape::geometryDestroyed(QObject *geometry)
{
Q_ASSERT(m_geometry == geometry);
// Set geometry to null and the old one will be disconnected and dereferenced
setGeometry(nullptr);
}
void QMeshShape::geometryContentChanged()
{
Q_ASSERT(m_geometry != nullptr);
QQuick3DPhysicsMeshManager::releaseMesh(m_mesh);
m_mesh = QQuick3DPhysicsMeshManager::getMesh(m_geometry);
updatePhysXGeometry();
m_dirtyPhysx = true;
emit needsRebuild(this);
}
QT_END_NAMESPACE