diff options
author | Tarja Sundqvist <tarja.sundqvist@qt.io> | 2025-03-04 15:48:16 +0200 |
---|---|---|
committer | Tarja Sundqvist <tarja.sundqvist@qt.io> | 2025-03-04 15:48:16 +0200 |
commit | d8004c7ef00cf19a35f151956d9dbd97dac4fd72 (patch) | |
tree | a64cad250c7b585a13e3fec3b6e9004cfc7d9654 | |
parent | 20cb76bd79e66311545ebae7ec14d2f1c9e4d631 (diff) | |
parent | 6dab0079351fe3c3383f4f2957f424bc4a8d0242 (diff) |
Merge tag 'v6.5.5-lts-lgpl' into 6.56.5
Qt 6.5.5-lts-lgpl release
27 files changed, 812 insertions, 475 deletions
diff --git a/.cmake.conf b/.cmake.conf index b46669a..faef28e 100644 --- a/.cmake.conf +++ b/.cmake.conf @@ -1,2 +1,2 @@ -set(QT_REPO_MODULE_VERSION "6.5.4") +set(QT_REPO_MODULE_VERSION "6.5.5") set(QT_EXTRA_INTERNAL_TARGET_DEFINES "QT_NO_AS_CONST=1") diff --git a/dependencies.yaml b/dependencies.yaml index 4e49c7e..20783f2 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -1,13 +1,13 @@ dependencies: ../tqtc-qtbase: - ref: 8ff0b254e4c3db81254782262d827f7831d15f6b + ref: fdf57f5df57e7d12cf871699d857a71acf272e0c required: true ../tqtc-qtdeclarative: - ref: 9edb471d3a35b3dc40def86c395789086edaa983 + ref: 7ac842cba18be081ac835bf40ac475ec4c47d30b required: true ../tqtc-qtquick3d: - ref: 2c64e3fbf973f0db7b33d18220b0ad84a47c11d0 + ref: d3cf878098cf399b00ecfa741e3d9d7d7964e42b required: true ../tqtc-qtshadertools: - ref: 5bf9de4a39fe2385998307c3e5ae16df6c0a5bd7 + ref: 8191dce7e16f9bf42476c41a3b7aab9a62b26daa required: true diff --git a/examples/quick3dphysics/cannon/main.qml b/examples/quick3dphysics/cannon/main.qml index bf11e61..6a16d7e 100644 --- a/examples/quick3dphysics/cannon/main.qml +++ b/examples/quick3dphysics/cannon/main.qml @@ -117,11 +117,11 @@ Window { return instancesSpheres.forEach(sphere => { - sphere.collisionShapes = {} + sphere.collisionShapes = [] sphere.destroy() }) instancesBoxes.forEach(box => { - box.object.collisionShapes = {} + box.object.collisionShapes = [] box.object.destroy() }) instancesSpheres = [] diff --git a/examples/quick3dphysics/material/main.qml b/examples/quick3dphysics/material/main.qml index 0e770e0..49a8d71 100644 --- a/examples/quick3dphysics/material/main.qml +++ b/examples/quick3dphysics/material/main.qml @@ -63,6 +63,8 @@ Window { materials: DefaultMaterial { diffuseColor: "green" } + castsShadows: false + receivesShadows: true } } //! [floor] diff --git a/src/3rdparty/PhysX/CMakeLists.txt b/src/3rdparty/PhysX/CMakeLists.txt index 366a9c0..1fb4923 100644 --- a/src/3rdparty/PhysX/CMakeLists.txt +++ b/src/3rdparty/PhysX/CMakeLists.txt @@ -1345,7 +1345,7 @@ if (MSYS OR MINGW) qt_internal_extend_target(BundledPhysX DEFINES PX_SIMD_DISABLED PX_GCC_FAMILY) endif() -if (UNIX) +if (UNIX OR MINGW) # Needed for PxPreprocessor.h error if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") qt_internal_extend_target(BundledPhysX DEFINES _DEBUG) diff --git a/src/quick3dphysics/CMakeLists.txt b/src/quick3dphysics/CMakeLists.txt index 350a4bd..76b123d 100644 --- a/src/quick3dphysics/CMakeLists.txt +++ b/src/quick3dphysics/CMakeLists.txt @@ -34,6 +34,7 @@ qt_internal_add_qml_module(Quick3DPhysics qdebugdrawhelper.cpp qdebugdrawhelper_p.h qdynamicrigidbody.cpp qdynamicrigidbody_p.h qheightfieldshape.cpp qheightfieldshape_p.h + qmeshshape.cpp qmeshshape_p.h qphysicscommands.cpp qphysicscommands_p.h qphysicsmaterial.cpp qphysicsmaterial_p.h qphysicsmeshutils_p_p.h @@ -81,7 +82,7 @@ qt_internal_extend_target(qquick3dphysicsplugin Qt::Quick3DPhysicsPrivate ) -if (UNIX) +if (UNIX OR MINGW) # Needed for PxPreprocessor.h error if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") qt_internal_extend_target(Quick3DPhysics DEFINES _DEBUG) diff --git a/src/quick3dphysics/physxnode/qphysxactorbody.cpp b/src/quick3dphysics/physxnode/qphysxactorbody.cpp index 8d952d0..88a7b18 100644 --- a/src/quick3dphysics/physxnode/qphysxactorbody.cpp +++ b/src/quick3dphysics/physxnode/qphysxactorbody.cpp @@ -24,21 +24,12 @@ QT_BEGIN_NAMESPACE -static const QQuaternion kMinus90YawRotation = QQuaternion::fromEulerAngles(0, -90, 0); - -static inline bool fuzzyEquals(const physx::PxTransform &a, const physx::PxTransform &b) -{ - return qFuzzyCompare(a.p.x, b.p.x) && qFuzzyCompare(a.p.y, b.p.y) && qFuzzyCompare(a.p.z, b.p.z) - && qFuzzyCompare(a.q.x, b.q.x) && qFuzzyCompare(a.q.y, b.q.y) - && qFuzzyCompare(a.q.z, b.q.z) && qFuzzyCompare(a.q.w, b.q.w); -} - static physx::PxTransform getPhysXLocalTransform(const QQuick3DNode *node) { // Modify transforms to make the PhysX shapes match the QtQuick3D conventions if (qobject_cast<const QPlaneShape *>(node) != nullptr) { // Rotate the plane to make it match the built-in rectangle - const QQuaternion rotation = kMinus90YawRotation * node->rotation(); + const QQuaternion rotation = QPhysicsUtils::kMinus90YawRotation * node->rotation(); return physx::PxTransform(QPhysicsUtils::toPhysXType(node->position()), QPhysicsUtils::toPhysXType(rotation)); } else if (auto *hf = qobject_cast<const QHeightFieldShape *>(node)) { @@ -115,7 +106,7 @@ void QPhysXActorBody::markDirtyShapes() auto poseNew = getPhysXLocalTransform(collisionShapes[i]); auto poseOld = physXShapes[i]->getLocalPose(); - if (!fuzzyEquals(poseNew, poseOld)) { + if (!QPhysicsUtils::fuzzyEquals(poseNew, poseOld)) { setShapesDirty(true); break; } diff --git a/src/quick3dphysics/physxnode/qphysxstaticbody.cpp b/src/quick3dphysics/physxnode/qphysxstaticbody.cpp index 083cc64..18f5c37 100644 --- a/src/quick3dphysics/physxnode/qphysxstaticbody.cpp +++ b/src/quick3dphysics/physxnode/qphysxstaticbody.cpp @@ -14,13 +14,6 @@ QT_BEGIN_NAMESPACE -static inline bool fuzzyEquals(const physx::PxTransform &a, const physx::PxTransform &b) -{ - return qFuzzyCompare(a.p.x, b.p.x) && qFuzzyCompare(a.p.y, b.p.y) && qFuzzyCompare(a.p.z, b.p.z) - && qFuzzyCompare(a.q.x, b.q.x) && qFuzzyCompare(a.q.y, b.q.y) - && qFuzzyCompare(a.q.z, b.q.z) && qFuzzyCompare(a.q.w, b.q.w); -} - QPhysXStaticBody::QPhysXStaticBody(QStaticRigidBody *frontEnd) : QPhysXRigidBody(frontEnd) { } DebugDrawBodyType QPhysXStaticBody::getDebugDrawBodyType() @@ -36,7 +29,7 @@ void QPhysXStaticBody::sync(float deltaTime, QHash<QQuick3DNode *, QMatrix4x4> & const physx::PxTransform poseOld = actor->getGlobalPose(); // For performance we only update static objects if they have been moved - if (!fuzzyEquals(poseNew, poseOld)) + if (!QPhysicsUtils::fuzzyEquals(poseNew, poseOld)) actor->setGlobalPose(poseNew); QPhysXActorBody::sync(deltaTime, transformCache); } diff --git a/src/quick3dphysics/physxnode/qphysxworld.cpp b/src/quick3dphysics/physxnode/qphysxworld.cpp index 9d348d3..e5731bb 100644 --- a/src/quick3dphysics/physxnode/qphysxworld.cpp +++ b/src/quick3dphysics/physxnode/qphysxworld.cpp @@ -91,8 +91,9 @@ public: QAbstractPhysicsNode *other = static_cast<QAbstractPhysicsNode *>(pairHeader.actors[1]->userData); - if (!trigger || !other || !trigger->m_backendObject || !other->m_backendObject - || world->isNodeRemoved(trigger) || world->isNodeRemoved(other)) + if (!trigger || !other || world->isNodeRemoved(trigger) + || world->isNodeRemoved(other) || !trigger->m_backendObject + || !other->m_backendObject) continue; const bool triggerReceive = @@ -130,9 +131,9 @@ public: } if (triggerReceive) - trigger->registerContact(other, positions, impulses, normals); + world->registerContact(other, trigger, positions, impulses, normals); if (otherReceive) - other->registerContact(trigger, positions, impulses, normalsInverted); + world->registerContact(trigger, other, positions, impulses, normalsInverted); } } }; diff --git a/src/quick3dphysics/qconvexmeshshape.cpp b/src/quick3dphysics/qconvexmeshshape.cpp index a8a420e..56b6d3f 100644 --- a/src/quick3dphysics/qconvexmeshshape.cpp +++ b/src/quick3dphysics/qconvexmeshshape.cpp @@ -1,248 +1,10 @@ // Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only -#include "qcacheutils_p.h" #include "qconvexmeshshape_p.h" -#include <QFile> -#include <QFileInfo> -#include <QtQuick3D/QQuick3DGeometry> -#include <extensions/PxExtensionsAPI.h> - -#include "foundation/PxVec3.h" -#include "cooking/PxConvexMeshDesc.h" -#include "extensions/PxDefaultStreams.h" - -#include <QtQml/qqml.h> -#include <QtQml/QQmlFile> -#include <QtQml/qqmlcontext.h> - -#include <QtQuick3DUtils/private/qssgmesh_p.h> -#include "qphysicsworld_p.h" -#include "qphysicsmeshutils_p_p.h" -#include "qphysicsutils_p.h" - QT_BEGIN_NAMESPACE -physx::PxConvexMesh *QQuick3DPhysicsMesh::convexMesh() -{ - if (m_convexMesh != nullptr) - return m_convexMesh; - - physx::PxPhysics *thePhysics = QPhysicsWorld::getPhysics(); - if (thePhysics == nullptr) - return nullptr; - - 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; - - physx::PxDefaultMemoryOutputStream buf; - physx::PxConvexMeshCookingResult::Enum result; - int vStride = m_ssgMesh.vertexBuffer().stride; - int vCount = m_ssgMesh.vertexBuffer().data.size() / vStride; - const auto *vd = m_ssgMesh.vertexBuffer().data.constData(); - - qCDebug(lcQuick3dPhysics) << "prepare cooking" << vCount << "verts"; - - QVector<physx::PxVec3> verts; - - for (int i = 0; i < vCount; ++i) { - auto *vp = reinterpret_cast<const QVector3D *>(vd + vStride * i + m_posOffset); - verts << physx::PxVec3 { vp->x(), vp->y(), vp->z() }; - } - - const auto *convexVerts = verts.constData(); - - physx::PxConvexMeshDesc convexDesc; - convexDesc.points.count = vCount; - convexDesc.points.stride = sizeof(physx::PxVec3); - convexDesc.points.data = convexVerts; - convexDesc.flags = physx::PxConvexFlag::eCOMPUTE_CONVEX; - - const auto cooking = QPhysicsWorld::getCooking(); - if (cooking && cooking->cookConvexMesh(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::PxTriangleMesh *QQuick3DPhysicsMesh::triangleMesh() -{ - - if (m_triangleMesh != nullptr) - return m_triangleMesh; - - physx::PxPhysics *thePhysics = QPhysicsWorld::getPhysics(); - if (thePhysics == nullptr) - return nullptr; - - 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; - - physx::PxDefaultMemoryOutputStream buf; - physx::PxTriangleMeshCookingResult::Enum result; - const int vStride = m_ssgMesh.vertexBuffer().stride; - const int vCount = m_ssgMesh.vertexBuffer().data.size() / vStride; - const auto *vd = m_ssgMesh.vertexBuffer().data.constData(); - - const int iStride = - m_ssgMesh.indexBuffer().componentType == QSSGMesh::Mesh::ComponentType::UnsignedInt16 - ? 2 - : 4; - const int iCount = m_ssgMesh.indexBuffer().data.size() / iStride; - - qCDebug(lcQuick3dPhysics) << "prepare cooking" << vCount << "verts" << iCount << "idxs"; - - physx::PxTriangleMeshDesc triangleDesc; - triangleDesc.points.count = vCount; - triangleDesc.points.stride = vStride; - triangleDesc.points.data = vd + m_posOffset; - - triangleDesc.flags = {}; //??? physx::PxMeshFlag::eFLIPNORMALS or - // physx::PxMeshFlag::e16_BIT_INDICES - triangleDesc.triangles.count = iCount / 3; - triangleDesc.triangles.stride = iStride * 3; - triangleDesc.triangles.data = m_ssgMesh.indexBuffer().data.constData(); - - const auto cooking = QPhysicsWorld::getCooking(); - if (cooking && cooking->cookTriangleMesh(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; -} - -void QQuick3DPhysicsMesh::loadSsgMesh() -{ - if (m_ssgMesh.isValid()) - return; - - static const char *compTypes[] = { "Null", "UnsignedInt8", "Int8", "UnsignedInt16", - "Int16", "UnsignedInt32", "Int32", "UnsignedInt64", - "Int64", "Float16", "Float32", "Float64" }; - - QFileInfo fileInfo = QFileInfo(m_meshPath); - if (fileInfo.exists()) { - QFile file(fileInfo.absoluteFilePath()); - if (file.open(QFile::ReadOnly)) - m_ssgMesh = QSSGMesh::Mesh::loadMesh(&file); - } - 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, - const QObject *contextObject) -{ - const QQmlContext *context = qmlContext(contextObject); - const auto resolvedUrl = context ? context->resolvedUrl(source) : source; - const auto qmlSource = QQmlFile::urlToLocalFileOrQrc(resolvedUrl); - auto *mesh = meshHash.value(qmlSource); - if (!mesh) { - mesh = new QQuick3DPhysicsMesh(qmlSource); - meshHash[qmlSource] = mesh; - } - mesh->ref(); - return mesh; -} - -void QQuick3DPhysicsMeshManager::releaseMesh(QQuick3DPhysicsMesh *mesh) -{ - if (mesh->deref() == 0) { - qCDebug(lcQuick3dPhysics()) << "deleting mesh" << mesh; - erase_if(meshHash, [mesh](std::pair<const QString &, QQuick3DPhysicsMesh *&> h) { - return h.second == mesh; - }); - delete mesh; - } -} - -QHash<QString, QQuick3DPhysicsMesh *> QQuick3DPhysicsMeshManager::meshHash; - /*! \qmltype ConvexMeshShape \inherits CollisionShape @@ -268,57 +30,14 @@ QHash<QString, QQuick3DPhysicsMesh *> QQuick3DPhysicsMeshManager::meshHash; for details. */ -QConvexMeshShape::QConvexMeshShape() = default; - -QConvexMeshShape::~QConvexMeshShape() +QMeshShape::MeshType QConvexMeshShape::shapeType() const { - delete m_meshGeometry; - if (m_mesh) - QQuick3DPhysicsMeshManager::releaseMesh(m_mesh); + return QMeshShape::MeshType::CONVEX; } -physx::PxGeometry *QConvexMeshShape::getPhysXGeometry() +bool QConvexMeshShape::isStaticShape() const { - if (m_dirtyPhysx || m_scaleDirty) { - updatePhysXGeometry(); - } - return m_meshGeometry; -} - -void QConvexMeshShape::updatePhysXGeometry() -{ - delete m_meshGeometry; - m_meshGeometry = nullptr; - - auto *convexMesh = m_mesh->convexMesh(); - if (!convexMesh) - return; - - auto meshScale = sceneScale(); - physx::PxMeshScale scale(physx::PxVec3(meshScale.x(), meshScale.y(), meshScale.z()), - physx::PxQuat(physx::PxIdentity)); - - m_meshGeometry = new physx::PxConvexMeshGeometry(convexMesh, scale); - m_dirtyPhysx = false; -} - -const QUrl &QConvexMeshShape::source() const -{ - return m_meshSource; -} - -void QConvexMeshShape::setSource(const QUrl &newSource) -{ - if (m_meshSource == newSource) - return; - m_meshSource = newSource; - m_mesh = QQuick3DPhysicsMeshManager::getMesh(m_meshSource, this); - updatePhysXGeometry(); - - m_dirtyPhysx = true; - - emit needsRebuild(this); - emit sourceChanged(); + return false; } QT_END_NAMESPACE diff --git a/src/quick3dphysics/qconvexmeshshape_p.h b/src/quick3dphysics/qconvexmeshshape_p.h index 75ddad9..de07690 100644 --- a/src/quick3dphysics/qconvexmeshshape_p.h +++ b/src/quick3dphysics/qconvexmeshshape_p.h @@ -15,46 +15,16 @@ // We mean it. // -#include <QtQuick3DPhysics/qtquick3dphysicsglobal.h> -#include <QtQuick3DPhysics/private/qabstractcollisionshape_p.h> -#include <QtCore/QObject> -#include <QtGui/QVector3D> -#include <QtQml/QQmlEngine> - -namespace physx { -class PxBoxGeometry; -class PxConvexMesh; -class PxConvexMeshGeometry; -} +#include "qmeshshape_p.h" QT_BEGIN_NAMESPACE -class QQuick3DPhysicsMesh; -class Q_QUICK3DPHYSICS_EXPORT QConvexMeshShape : public QAbstractCollisionShape +class Q_QUICK3DPHYSICS_EXPORT QConvexMeshShape : public QMeshShape { Q_OBJECT - Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged REVISION(6, 5)) QML_NAMED_ELEMENT(ConvexMeshShape) -public: - QConvexMeshShape(); - ~QConvexMeshShape(); - - physx::PxGeometry *getPhysXGeometry() override; - - Q_REVISION(6, 5) const QUrl &source() const; - Q_REVISION(6, 5) void setSource(const QUrl &newSource); - bool isStaticShape() const override { return false; } - -signals: - Q_REVISION(6, 5) void sourceChanged(); - -private: - void updatePhysXGeometry(); - - bool m_dirtyPhysx = false; - physx::PxConvexMeshGeometry *m_meshGeometry = nullptr; - QUrl m_meshSource; - QQuick3DPhysicsMesh *m_mesh = nullptr; + virtual QMeshShape::MeshType shapeType() const override; + virtual bool isStaticShape() const override; }; QT_END_NAMESPACE diff --git a/src/quick3dphysics/qmeshshape.cpp b/src/quick3dphysics/qmeshshape.cpp new file mode 100644 index 0000000..f7d38da --- /dev/null +++ b/src/quick3dphysics/qmeshshape.cpp @@ -0,0 +1,310 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "qcacheutils_p.h" +#include "qmeshshape_p.h" + +#include <QFile> +#include <QFileInfo> +#include <QtQuick3D/QQuick3DGeometry> +#include <extensions/PxExtensionsAPI.h> + +#include "foundation/PxVec3.h" +#include "cooking/PxConvexMeshDesc.h" +#include "extensions/PxDefaultStreams.h" + +#include <QtQml/qqml.h> +#include <QtQml/QQmlFile> +#include <QtQml/qqmlcontext.h> + +#include <QtQuick3DUtils/private/qssgmesh_p.h> +#include "qmeshshape_p.h" +#include "qphysicsworld_p.h" +#include "qphysicsmeshutils_p_p.h" + +QT_BEGIN_NAMESPACE + +physx::PxConvexMesh *QQuick3DPhysicsMesh::convexMesh() +{ + if (m_convexMesh != nullptr) + return m_convexMesh; + + physx::PxPhysics *thePhysics = QPhysicsWorld::getPhysics(); + if (thePhysics == nullptr) + return nullptr; + + 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; + + physx::PxDefaultMemoryOutputStream buf; + physx::PxConvexMeshCookingResult::Enum result; + int vStride = m_ssgMesh.vertexBuffer().stride; + int vCount = m_ssgMesh.vertexBuffer().data.size() / vStride; + const auto *vd = m_ssgMesh.vertexBuffer().data.constData(); + + qCDebug(lcQuick3dPhysics) << "prepare cooking" << vCount << "verts"; + + QVector<physx::PxVec3> verts; + + for (int i = 0; i < vCount; ++i) { + auto *vp = reinterpret_cast<const QVector3D *>(vd + vStride * i + m_posOffset); + verts << physx::PxVec3 { vp->x(), vp->y(), vp->z() }; + } + + const auto *convexVerts = verts.constData(); + + physx::PxConvexMeshDesc convexDesc; + convexDesc.points.count = vCount; + convexDesc.points.stride = sizeof(physx::PxVec3); + convexDesc.points.data = convexVerts; + convexDesc.flags = physx::PxConvexFlag::eCOMPUTE_CONVEX; + + const auto cooking = QPhysicsWorld::getCooking(); + if (cooking && cooking->cookConvexMesh(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::PxTriangleMesh *QQuick3DPhysicsMesh::triangleMesh() +{ + if (m_triangleMesh != nullptr) + return m_triangleMesh; + + physx::PxPhysics *thePhysics = QPhysicsWorld::getPhysics(); + if (thePhysics == nullptr) + return nullptr; + + 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; + + physx::PxDefaultMemoryOutputStream buf; + physx::PxTriangleMeshCookingResult::Enum result; + const int vStride = m_ssgMesh.vertexBuffer().stride; + const int vCount = m_ssgMesh.vertexBuffer().data.size() / vStride; + const auto *vd = m_ssgMesh.vertexBuffer().data.constData(); + + const int iStride = + m_ssgMesh.indexBuffer().componentType == QSSGMesh::Mesh::ComponentType::UnsignedInt16 + ? 2 + : 4; + const int iCount = m_ssgMesh.indexBuffer().data.size() / iStride; + + qCDebug(lcQuick3dPhysics) << "prepare cooking" << vCount << "verts" << iCount << "idxs"; + + physx::PxTriangleMeshDesc triangleDesc; + triangleDesc.points.count = vCount; + triangleDesc.points.stride = vStride; + triangleDesc.points.data = vd + m_posOffset; + + triangleDesc.flags = {}; //??? physx::PxMeshFlag::eFLIPNORMALS or + // physx::PxMeshFlag::e16_BIT_INDICES + triangleDesc.triangles.count = iCount / 3; + triangleDesc.triangles.stride = iStride * 3; + triangleDesc.triangles.data = m_ssgMesh.indexBuffer().data.constData(); + + const auto cooking = QPhysicsWorld::getCooking(); + if (cooking && cooking->cookTriangleMesh(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; +} + +void QQuick3DPhysicsMesh::loadSsgMesh() +{ + if (m_ssgMesh.isValid()) + return; + + static const char *compTypes[] = { "Null", "UnsignedInt8", "Int8", "UnsignedInt16", + "Int16", "UnsignedInt32", "Int32", "UnsignedInt64", + "Int64", "Float16", "Float32", "Float64" }; + + QFileInfo fileInfo = QFileInfo(m_meshPath); + if (fileInfo.exists()) { + QFile file(fileInfo.absoluteFilePath()); + if (file.open(QFile::ReadOnly)) + m_ssgMesh = QSSGMesh::Mesh::loadMesh(&file); + } + 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, + const QObject *contextObject) +{ + const QQmlContext *context = qmlContext(contextObject); + const auto resolvedUrl = context ? context->resolvedUrl(source) : source; + const auto qmlSource = QQmlFile::urlToLocalFileOrQrc(resolvedUrl); + auto *mesh = meshHash.value(qmlSource); + if (!mesh) { + mesh = new QQuick3DPhysicsMesh(qmlSource); + meshHash[qmlSource] = mesh; + } + mesh->ref(); + return mesh; +} + +void QQuick3DPhysicsMeshManager::releaseMesh(QQuick3DPhysicsMesh *mesh) +{ + if (mesh->deref() == 0) { + qCDebug(lcQuick3dPhysics()) << "deleting mesh" << mesh; + erase_if(meshHash, [mesh](std::pair<const QString &, QQuick3DPhysicsMesh *&> h) { + return h.second == mesh; + }); + delete mesh; + } +} + +QHash<QString, QQuick3DPhysicsMesh *> QQuick3DPhysicsMeshManager::meshHash; + +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 (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; + m_mesh = QQuick3DPhysicsMeshManager::getMesh(m_meshSource, this); + updatePhysXGeometry(); + + m_dirtyPhysx = true; + + emit needsRebuild(this); + emit sourceChanged(); +} + +QT_END_NAMESPACE diff --git a/src/quick3dphysics/qmeshshape_p.h b/src/quick3dphysics/qmeshshape_p.h new file mode 100644 index 0000000..5ce34b2 --- /dev/null +++ b/src/quick3dphysics/qmeshshape_p.h @@ -0,0 +1,68 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef MESHSHAPE_H +#define MESHSHAPE_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 <QtQuick3DPhysics/qtquick3dphysicsglobal.h> +#include <QtQuick3DPhysics/private/qabstractcollisionshape_p.h> +#include <QtCore/QObject> +#include <QtGui/QVector3D> +#include <QtQml/QQmlEngine> + +namespace physx { +class PxBoxGeometry; +class PxConvexMesh; +class PxConvexMeshGeometry; +class PxTriangleMesh; +class PxTriangleMeshGeometry; +} + +QT_BEGIN_NAMESPACE +class QQuick3DPhysicsMesh; + +class Q_QUICK3DPHYSICS_EXPORT QMeshShape : public QAbstractCollisionShape +{ + Q_OBJECT + Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged REVISION(6, 5)) + QML_NAMED_ELEMENT(MeshShape) + QML_UNCREATABLE("abstract interface") + +public: + ~QMeshShape(); + + enum class MeshType { TRIANGLE, CONVEX }; + virtual MeshType shapeType() const = 0; + + physx::PxGeometry *getPhysXGeometry() override; + + Q_REVISION(6, 5) const QUrl &source() const; + Q_REVISION(6, 5) void setSource(const QUrl &newSource); + +signals: + Q_REVISION(6, 5) void sourceChanged(); + +private: + void updatePhysXGeometry(); + + bool m_dirtyPhysx = false; + physx::PxConvexMeshGeometry *m_convexGeometry = nullptr; + physx::PxTriangleMeshGeometry *m_triangleGeometry = nullptr; + QUrl m_meshSource; + QQuick3DPhysicsMesh *m_mesh = nullptr; +}; + +QT_END_NAMESPACE + +#endif // MESHSHAPE_H diff --git a/src/quick3dphysics/qphysicsutils_p.h b/src/quick3dphysics/qphysicsutils_p.h index 8f2a0bb..ad1d679 100644 --- a/src/quick3dphysics/qphysicsutils_p.h +++ b/src/quick3dphysics/qphysicsutils_p.h @@ -59,6 +59,15 @@ Q_ALWAYS_INLINE physx::PxTransform toPhysXTransform(const QVector3D &position, return physx::PxTransform(QPhysicsUtils::toPhysXType(position), QPhysicsUtils::toPhysXType(rotation)); } + +Q_ALWAYS_INLINE bool fuzzyEquals(const physx::PxTransform &a, const physx::PxTransform &b) +{ + return qFuzzyCompare(a.p.x, b.p.x) && qFuzzyCompare(a.p.y, b.p.y) && qFuzzyCompare(a.p.z, b.p.z) + && qFuzzyCompare(a.q.x, b.q.x) && qFuzzyCompare(a.q.y, b.q.y) + && qFuzzyCompare(a.q.z, b.q.z) && qFuzzyCompare(a.q.w, b.q.w); +} + +inline const QQuaternion kMinus90YawRotation = QQuaternion::fromEulerAngles(0, -90, 0); } #endif // QPHYSICSUTILS_P_H diff --git a/src/quick3dphysics/qphysicsworld.cpp b/src/quick3dphysics/qphysicsworld.cpp index 2376edd..be7c30d 100644 --- a/src/quick3dphysics/qphysicsworld.cpp +++ b/src/quick3dphysics/qphysicsworld.cpp @@ -146,8 +146,6 @@ QT_BEGIN_NAMESPACE Q_LOGGING_CATEGORY(lcQuick3dPhysics, "qt.quick3d.physics"); -static const QQuaternion kMinus90YawRotation = QQuaternion::fromEulerAngles(0, -90, 0); - ///////////////////////////////////////////////////////////////////////////// class SimulationWorker : public QObject @@ -204,6 +202,88 @@ private: ///////////////////////////////////////////////////////////////////////////// +void QPhysicsWorld::DebugModelHolder::releaseMeshPointer() +{ + if (auto base = static_cast<physx::PxBase *>(ptr); base) + base->release(); + ptr = nullptr; +} + +const QVector3D &QPhysicsWorld::DebugModelHolder::halfExtents() const +{ + return data; +} +void QPhysicsWorld::DebugModelHolder::setHalfExtents(const QVector3D &halfExtents) +{ + data = halfExtents; +} +float QPhysicsWorld::DebugModelHolder::radius() const +{ + return data.x(); +} +void QPhysicsWorld::DebugModelHolder::setRadius(float radius) +{ + data.setX(radius); +} +float QPhysicsWorld::DebugModelHolder::heightScale() const +{ + return data.x(); +} +void QPhysicsWorld::DebugModelHolder::setHeightScale(float heightScale) +{ + data.setX(heightScale); +} +float QPhysicsWorld::DebugModelHolder::halfHeight() const +{ + return data.y(); +} +void QPhysicsWorld::DebugModelHolder::setHalfHeight(float halfHeight) +{ + data.setY(halfHeight); +} +float QPhysicsWorld::DebugModelHolder::rowScale() const +{ + return data.y(); +} +void QPhysicsWorld::DebugModelHolder::setRowScale(float rowScale) +{ + data.setY(rowScale); +} +float QPhysicsWorld::DebugModelHolder::columnScale() const +{ + return data.z(); +} +void QPhysicsWorld::DebugModelHolder::setColumnScale(float columnScale) +{ + data.setZ(columnScale); +} +physx::PxConvexMesh *QPhysicsWorld::DebugModelHolder::getConvexMesh() +{ + return static_cast<physx::PxConvexMesh *>(ptr); +} +void QPhysicsWorld::DebugModelHolder::setConvexMesh(physx::PxConvexMesh *mesh) +{ + ptr = static_cast<void *>(mesh); +} +physx::PxTriangleMesh *QPhysicsWorld::DebugModelHolder::getTriangleMesh() +{ + return static_cast<physx::PxTriangleMesh *>(ptr); +} +void QPhysicsWorld::DebugModelHolder::setTriangleMesh(physx::PxTriangleMesh *mesh) +{ + ptr = static_cast<void *>(mesh); +} +physx::PxHeightField *QPhysicsWorld::DebugModelHolder::getHeightField() +{ + return static_cast<physx::PxHeightField *>(ptr); +} +void QPhysicsWorld::DebugModelHolder::setHeightField(physx::PxHeightField *hf) +{ + ptr = static_cast<physx::PxHeightField *>(hf); +} + +///////////////////////////////////////////////////////////////////////////// + struct QWorldManager { QVector<QPhysicsWorld *> worlds; @@ -226,18 +306,39 @@ void QPhysicsWorld::deregisterNode(QAbstractPhysicsNode *physicsNode) { for (auto world : worldManager.worlds) { world->m_newPhysicsNodes.removeAll(physicsNode); + QMutexLocker locker(&world->m_removedPhysicsNodesMutex); if (physicsNode->m_backendObject) { Q_ASSERT(physicsNode->m_backendObject->frontendNode == physicsNode); physicsNode->m_backendObject->frontendNode = nullptr; physicsNode->m_backendObject->isRemoved = true; physicsNode->m_backendObject = nullptr; } - QMutexLocker locker(&world->m_removedPhysicsNodesMutex); world->m_removedPhysicsNodes.insert(physicsNode); } worldManager.orphanNodes.removeAll(physicsNode); } +void QPhysicsWorld::registerContact(QAbstractPhysicsNode *sender, QAbstractPhysicsNode *receiver, + const QVector<QVector3D> &positions, + const QVector<QVector3D> &impulses, + const QVector<QVector3D> &normals) +{ + // Since collision callbacks happen in the physx simulation thread we need + // to store these callbacks. Otherwise, if an object is deleted in the same + // frame a 'onBodyContact' signal is enqueued and a crash will happen. + // Therefore we save these contact callbacks and run them at the end of the + // physics frame when we know if the objects are deleted or not. + + BodyContact contact; + contact.sender = sender; + contact.receiver = receiver; + contact.positions = positions; + contact.impulses = impulses; + contact.normals = normals; + + m_registeredContacts.push_back(contact); +} + QPhysicsWorld::QPhysicsWorld(QObject *parent) : QObject(parent) { m_inDesignStudio = !qEnvironmentVariableIsEmpty("QML_PUPPET_MODE"); @@ -369,6 +470,7 @@ void QPhysicsWorld::setViewport(QQuick3DNode *viewport) m_debugMaterials.clear(); for (auto &holder : m_collisionShapeDebugModels) { + holder.releaseMeshPointer(); delete holder.model; } m_collisionShapeDebugModels.clear(); @@ -441,11 +543,11 @@ void QPhysicsWorld::updateDebugDraw() { if (!(m_forceDebugDraw || m_hasIndividualDebugDraw)) { // Nothing to draw, trash all previous models (if any) and return - if (!m_collisionShapeDebugModels.isEmpty()) { - for (const auto& holder : std::as_const(m_collisionShapeDebugModels)) - delete holder.model; - m_collisionShapeDebugModels.clear(); + for (auto &holder : m_collisionShapeDebugModels) { + holder.releaseMeshPointer(); + delete holder.model; } + m_collisionShapeDebugModels.clear(); return; } @@ -560,7 +662,7 @@ void QPhysicsWorld::updateDebugDraw() physXShape->getPlaneGeometry(planeGeometry); // Special rotation const QQuaternion rotation = - kMinus90YawRotation * QPhysicsUtils::toQtType(localPose.q); + QPhysicsUtils::kMinus90YawRotation * QPhysicsUtils::toQtType(localPose.q); localPose = physx::PxTransform(localPose.p, QPhysicsUtils::toPhysXType(rotation)); if (model->geometry() == nullptr) { @@ -571,17 +673,31 @@ void QPhysicsWorld::updateDebugDraw() } break; + // For heightfield, convex mesh and triangle mesh we increase its reference count + // to make sure it does not get dereferenced and deleted so that the new mesh will + // have another memory address so we know when it has changed. case physx::PxGeometryType::eHEIGHTFIELD: { physx::PxHeightFieldGeometry heightFieldGeometry; - physXShape->getHeightFieldGeometry(heightFieldGeometry); + bool success = physXShape->getHeightFieldGeometry(heightFieldGeometry); + Q_ASSERT(success); const float heightScale = holder.heightScale(); const float rowScale = holder.rowScale(); const float columnScale = holder.columnScale(); + if (auto heightField = holder.getHeightField(); + heightField && heightField != heightFieldGeometry.heightField) { + heightField->release(); + holder.setHeightField(nullptr); + } + if (!qFuzzyCompare(heightFieldGeometry.heightScale, heightScale) || !qFuzzyCompare(heightFieldGeometry.rowScale, rowScale) - || !qFuzzyCompare(heightFieldGeometry.columnScale, columnScale)) { - + || !qFuzzyCompare(heightFieldGeometry.columnScale, columnScale) + || !holder.getHeightField()) { + if (!holder.getHeightField()) { + heightFieldGeometry.heightField->acquireReference(); + holder.setHeightField(heightFieldGeometry.heightField); + } auto geom = QDebugDrawHelper::generateHeightFieldGeometry( heightFieldGeometry.heightField, heightFieldGeometry.heightScale, heightFieldGeometry.rowScale, heightFieldGeometry.columnScale); @@ -596,12 +712,23 @@ void QPhysicsWorld::updateDebugDraw() case physx::PxGeometryType::eCONVEXMESH: { physx::PxConvexMeshGeometry convexMeshGeometry; - physXShape->getConvexMeshGeometry(convexMeshGeometry); + const bool success = physXShape->getConvexMeshGeometry(convexMeshGeometry); + Q_ASSERT(success); const auto rotation = convexMeshGeometry.scale.rotation * localPose.q; localPose = physx::PxTransform(localPose.p, rotation); model->setScale(QPhysicsUtils::toQtType(convexMeshGeometry.scale.scale)); - if (model->geometry() == nullptr) { + if (auto convexMesh = holder.getConvexMesh(); + convexMesh && convexMesh != convexMeshGeometry.convexMesh) { + convexMesh->release(); + holder.setConvexMesh(nullptr); + } + + if (!model->geometry() || !holder.getConvexMesh()) { + if (!holder.getConvexMesh()) { + convexMeshGeometry.convexMesh->acquireReference(); + holder.setConvexMesh(convexMeshGeometry.convexMesh); + } auto geom = QDebugDrawHelper::generateConvexMeshGeometry( convexMeshGeometry.convexMesh); geom->setParent(model); @@ -612,12 +739,23 @@ void QPhysicsWorld::updateDebugDraw() case physx::PxGeometryType::eTRIANGLEMESH: { physx::PxTriangleMeshGeometry triangleMeshGeometry; - physXShape->getTriangleMeshGeometry(triangleMeshGeometry); + const bool success = physXShape->getTriangleMeshGeometry(triangleMeshGeometry); + Q_ASSERT(success); const auto rotation = triangleMeshGeometry.scale.rotation * localPose.q; localPose = physx::PxTransform(localPose.p, rotation); model->setScale(QPhysicsUtils::toQtType(triangleMeshGeometry.scale.scale)); - if (model->geometry() == nullptr) { + if (auto triangleMesh = holder.getTriangleMesh(); + triangleMesh && triangleMesh != triangleMeshGeometry.triangleMesh) { + triangleMesh->release(); + holder.setTriangleMesh(nullptr); + } + + if (!model->geometry() || !holder.getTriangleMesh()) { + if (!holder.getTriangleMesh()) { + triangleMeshGeometry.triangleMesh->acquireReference(); + holder.setTriangleMesh(triangleMeshGeometry.triangleMesh); + } auto geom = QDebugDrawHelper::generateTriangleMeshGeometry( triangleMeshGeometry.triangleMesh); geom->setParent(model); @@ -648,6 +786,7 @@ void QPhysicsWorld::updateDebugDraw() DebugModelHolder>::iterator it) { if (!currentCollisionShapes.contains(it.key())) { auto holder = it.value(); + holder.releaseMeshPointer(); if (holder.model) delete holder.model; return true; @@ -830,6 +969,7 @@ void QPhysicsWorld::updateDebugDrawDesignStudio() DebugModelHolder>::iterator it) { if (!currentCollisionShapes.contains(it.key())) { auto holder = it.value(); + holder.releaseMeshPointer(); if (holder.model) { delete holder.geometry; delete holder.model; @@ -976,6 +1116,7 @@ void QPhysicsWorld::initPhysics() void QPhysicsWorld::frameFinished(float deltaTime) { matchOrphanNodes(); + emitContactCallbacks(); cleanupRemovedNodes(); for (auto *node : std::as_const(m_newPhysicsNodes)) { auto *body = node->createPhysXBackend(); @@ -1006,6 +1147,7 @@ void QPhysicsWorld::frameFinishedDesignStudio() { // Note sure if this is needed but do it anyway matchOrphanNodes(); + emitContactCallbacks(); cleanupRemovedNodes(); // Ignore new physics nodes, we find them from the scene node anyway m_newPhysicsNodes.clear(); @@ -1088,6 +1230,19 @@ void QPhysicsWorld::findPhysicsNodes() } } +void QPhysicsWorld::emitContactCallbacks() +{ + for (const QPhysicsWorld::BodyContact &contact : m_registeredContacts) { + if (m_removedPhysicsNodes.contains(contact.sender) + || m_removedPhysicsNodes.contains(contact.receiver)) + continue; + contact.receiver->registerContact(contact.sender, contact.positions, contact.impulses, + contact.normals); + } + + m_registeredContacts.clear(); +} + physx::PxPhysics *QPhysicsWorld::getPhysics() { return StaticPhysXObjects::getReference().physics; diff --git a/src/quick3dphysics/qphysicsworld_p.h b/src/quick3dphysics/qphysicsworld_p.h index f36a7b2..f749721 100644 --- a/src/quick3dphysics/qphysicsworld_p.h +++ b/src/quick3dphysics/qphysicsworld_p.h @@ -36,6 +36,9 @@ class PxRigidActor; class PxRigidStatic; class PxCooking; class PxControllerManager; +class PxConvexMesh; +class PxTriangleMesh; +class PxHeightField; } QT_BEGIN_NAMESPACE @@ -101,6 +104,10 @@ public: static void registerNode(QAbstractPhysicsNode *physicsNode); static void deregisterNode(QAbstractPhysicsNode *physicsNode); + void registerContact(QAbstractPhysicsNode *sender, QAbstractPhysicsNode *receiver, + const QVector<QVector3D> &positions, const QVector<QVector3D> &impulses, + const QVector<QVector3D> &normals); + Q_REVISION(6, 5) QQuick3DNode *viewport() const; void setHasIndividualDebugDraw(); physx::PxControllerManager *controllerManager(); @@ -145,30 +152,52 @@ private: void disableDebugDraw(); void matchOrphanNodes(); void findPhysicsNodes(); + void emitContactCallbacks(); + + struct BodyContact + { + QAbstractPhysicsNode *sender = nullptr; + QAbstractPhysicsNode *receiver = nullptr; + QVector<QVector3D> positions; + QVector<QVector3D> impulses; + QVector<QVector3D> normals; + }; struct DebugModelHolder { QQuick3DModel *model = nullptr; QQuick3DGeometry *geometry = nullptr; QVector3D data; + void *ptr = nullptr; + + void releaseMeshPointer(); + + const QVector3D &halfExtents() const; + void setHalfExtents(const QVector3D &halfExtents); + + float radius() const; + void setRadius(float radius); + + float heightScale() const; + void setHeightScale(float heightScale); - const QVector3D &halfExtents() const { return data; } - void setHalfExtents(const QVector3D &halfExtents) { data = halfExtents; } + float halfHeight() const; + void setHalfHeight(float halfHeight); - float radius() const { return data.x(); } - void setRadius(float radius) { data.setX(radius); } + float rowScale() const; + void setRowScale(float rowScale); - float heightScale() const { return data.x(); } - void setHeightScale(float heightScale) { data.setX(heightScale); } + float columnScale() const; + void setColumnScale(float columnScale); - float halfHeight() const { return data.y(); } - void setHalfHeight(float halfHeight) { data.setY(halfHeight); } + physx::PxConvexMesh *getConvexMesh(); + void setConvexMesh(physx::PxConvexMesh *mesh); - float rowScale() const { return data.y(); } - void setRowScale(float rowScale) { data.setY(rowScale); } + physx::PxTriangleMesh *getTriangleMesh(); + void setTriangleMesh(physx::PxTriangleMesh *mesh); - float columnScale() const { return data.z(); } - void setColumnScale(float columnScale) { data.setZ(columnScale); } + physx::PxHeightField *getHeightField(); + void setHeightField(physx::PxHeightField *hf); }; QList<QAbstractPhysXNode *> m_physXBodies; @@ -179,6 +208,7 @@ private: m_collisionShapeDebugModels; QSet<QAbstractPhysicsNode *> m_removedPhysicsNodes; QMutex m_removedPhysicsNodesMutex; + QList<BodyContact> m_registeredContacts; QVector3D m_gravity = QVector3D(0.f, -981.f, 0.f); float m_typicalLength = 100.f; // 100 cm diff --git a/src/quick3dphysics/qtrianglemeshshape.cpp b/src/quick3dphysics/qtrianglemeshshape.cpp index f804d48..c8cde78 100644 --- a/src/quick3dphysics/qtrianglemeshshape.cpp +++ b/src/quick3dphysics/qtrianglemeshshape.cpp @@ -3,17 +3,6 @@ #include "qtrianglemeshshape_p.h" -#include <QtQuick3D/QQuick3DGeometry> -#include <extensions/PxExtensionsAPI.h> - -#include "qphysicsmeshutils_p_p.h" - -//######################################################################################## -// NOTE: -// Triangle mesh, heightfield or plane geometry shapes configured as eSIMULATION_SHAPE are -// not supported for non-kinematic PxRigidDynamic instances. -//######################################################################################## - QT_BEGIN_NAMESPACE /*! @@ -42,58 +31,14 @@ QT_BEGIN_NAMESPACE for details. */ -QTriangleMeshShape::QTriangleMeshShape() = default; - -QTriangleMeshShape::~QTriangleMeshShape() -{ - delete m_meshGeometry; - if (m_mesh) - QQuick3DPhysicsMeshManager::releaseMesh(m_mesh); -} - -physx::PxGeometry *QTriangleMeshShape::getPhysXGeometry() -{ - if (m_dirtyPhysx || m_scaleDirty) { - updatePhysXGeometry(); - } - return m_meshGeometry; -} - -void QTriangleMeshShape::updatePhysXGeometry() +QMeshShape::MeshType QTriangleMeshShape::shapeType() const { - delete m_meshGeometry; - m_meshGeometry = nullptr; - - if (!m_mesh) - return; - auto *triangleMesh = m_mesh->triangleMesh(); - if (!triangleMesh) - return; - - auto meshScale = sceneScale(); - physx::PxMeshScale scale(physx::PxVec3(meshScale.x(), meshScale.y(), meshScale.z()), - physx::PxQuat(physx::PxIdentity)); - - m_meshGeometry = new physx::PxTriangleMeshGeometry(triangleMesh, scale); - m_dirtyPhysx = false; + return QMeshShape::MeshType::TRIANGLE; } -const QUrl &QTriangleMeshShape::source() const +bool QTriangleMeshShape::isStaticShape() const { - return m_meshSource; -} - -void QTriangleMeshShape::setSource(const QUrl &newSource) -{ - if (m_meshSource == newSource) - return; - m_meshSource = newSource; - m_mesh = QQuick3DPhysicsMeshManager::getMesh(m_meshSource, this); - - updatePhysXGeometry(); - - emit needsRebuild(this); - emit sourceChanged(); + return true; } QT_END_NAMESPACE diff --git a/src/quick3dphysics/qtrianglemeshshape_p.h b/src/quick3dphysics/qtrianglemeshshape_p.h index a974e50..c4f7e36 100644 --- a/src/quick3dphysics/qtrianglemeshshape_p.h +++ b/src/quick3dphysics/qtrianglemeshshape_p.h @@ -15,47 +15,16 @@ // We mean it. // -#include <QtQuick3DPhysics/qtquick3dphysicsglobal.h> -#include <QtQuick3DPhysics/private/qabstractcollisionshape_p.h> -#include <QtCore/QObject> -#include <QtGui/QVector3D> -#include <QtQml/QQmlEngine> - -namespace physx { -class PxBoxGeometry; -class PxTriangleMesh; -class PxTriangleMeshGeometry; -} +#include "qmeshshape_p.h" QT_BEGIN_NAMESPACE -class QQuick3DPhysicsMesh; -class Q_QUICK3DPHYSICS_EXPORT QTriangleMeshShape : public QAbstractCollisionShape +class Q_QUICK3DPHYSICS_EXPORT QTriangleMeshShape : public QMeshShape { Q_OBJECT - Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged REVISION(6, 5)) - QML_NAMED_ELEMENT(TriangleMeshShape) -public: - QTriangleMeshShape(); - ~QTriangleMeshShape(); - - physx::PxGeometry *getPhysXGeometry() override; - Q_REVISION(6, 5) const QUrl &source() const; - Q_REVISION(6, 5) void setSource(const QUrl &newSource); - bool isStaticShape() const override { return true; } - -signals: - Q_REVISION(6, 5) void sourceChanged(); - -private: - void updatePhysXGeometry(); - - bool m_dirtyPhysx = false; - QUrl m_meshSource; - QQuick3DPhysicsMesh *m_mesh = nullptr; - - physx::PxTriangleMeshGeometry *m_meshGeometry = nullptr; + virtual QMeshShape::MeshType shapeType() const override; + virtual bool isStaticShape() const override; }; QT_END_NAMESPACE diff --git a/tests/auto/CMakeLists.txt b/tests/auto/CMakeLists.txt index 7a23a0d..17f692b 100644 --- a/tests/auto/CMakeLists.txt +++ b/tests/auto/CMakeLists.txt @@ -1,3 +1,4 @@ +add_subdirectory(callback_create_delete_node) add_subdirectory(changescene) add_subdirectory(character) add_subdirectory(character_remove) diff --git a/tests/auto/callback_create_delete_node/Box.qml b/tests/auto/callback_create_delete_node/Box.qml new file mode 100644 index 0000000..a8137b3 --- /dev/null +++ b/tests/auto/callback_create_delete_node/Box.qml @@ -0,0 +1,21 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick3D +import QtQuick3D.Physics + +DynamicRigidBody { + Model { + source: "#Cube" + materials: PrincipledMaterial { + baseColor: "red" + } + } + + sendContactReports: true + receiveContactReports: true + onBodyContact: (body, positions, impulses, normals) => {} + + collisionShapes: BoxShape {} +} diff --git a/tests/auto/callback_create_delete_node/CMakeLists.txt b/tests/auto/callback_create_delete_node/CMakeLists.txt new file mode 100644 index 0000000..2cc871e --- /dev/null +++ b/tests/auto/callback_create_delete_node/CMakeLists.txt @@ -0,0 +1,22 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +set(PROJECT_NAME "test_auto_callback_create_delete_node") + +qt_internal_add_test(${PROJECT_NAME} + GUI + QMLTEST + SOURCES + tst_callback_create_delete_node.cpp + LIBRARIES + Qt::Core + Qt::Qml + TESTDATA + tst_callback_create_delete_node.qml + Box.qml + BUILTIN_TESTDATA +) + +if(QT_BUILD_STANDALONE_TESTS) + qt_import_qml_plugins(${PROJECT_NAME}) +endif() diff --git a/tests/auto/callback_create_delete_node/tst_callback_create_delete_node.cpp b/tests/auto/callback_create_delete_node/tst_callback_create_delete_node.cpp new file mode 100644 index 0000000..1e4324f --- /dev/null +++ b/tests/auto/callback_create_delete_node/tst_callback_create_delete_node.cpp @@ -0,0 +1,21 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include <QtQuickTest/quicktest.h> +class tst_callback_create_delete_node : public QObject +{ + Q_OBJECT +private slots: + void skiptest() { QSKIP("This test will fail, skipping."); }; +}; +int main(int argc, char **argv) +{ + if (!qEnvironmentVariableIsEmpty("QEMU_LD_PREFIX")) { + qWarning("This test is unstable on QEMU, so it will be skipped."); + tst_callback_create_delete_node skip; + return QTest::qExec(&skip, argc, argv); + } + QTEST_SET_MAIN_SOURCE_PATH + return quick_test_main(argc, argv, "tst_callback_create_delete_node", QUICK_TEST_SOURCE_DIR); +} +#include "tst_callback_create_delete_node.moc" diff --git a/tests/auto/callback_create_delete_node/tst_callback_create_delete_node.qml b/tests/auto/callback_create_delete_node/tst_callback_create_delete_node.qml new file mode 100644 index 0000000..c43db93 --- /dev/null +++ b/tests/auto/callback_create_delete_node/tst_callback_create_delete_node.qml @@ -0,0 +1,104 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +// Tests that removing and adding objects with active contact callbacks +// does not crash. QTBUG-121033 + +import QtCore +import QtTest +import QtQuick3D +import QtQuick3D.Physics +import QtQuick3D.Physics.Helpers +import QtQuick + +Item { + width: 800 + height: 600 + visible: true + + PhysicsWorld { + scene: viewport.scene + } + + View3D { + id: viewport + width: parent.width + height: parent.height + focus: true + + environment: SceneEnvironment { + antialiasingMode: SceneEnvironment.MSAA + backgroundMode: SceneEnvironment.Color + clearColor: "#f0f0f0" + } + + PerspectiveCamera { + id: camera + position: Qt.vector3d(-400, 500, 1000) + eulerRotation: Qt.vector3d(-20, -20, 0) + clipFar: 5000 + clipNear: 1 + } + + DirectionalLight { + eulerRotation: Qt.vector3d(-45, 45, 0) + } + + Node { + id: shapeSpawner + property var instancesBoxes: [] + property var boxComponent: Qt.createComponent("Box.qml") + property int numSpawns: 0 + + function createStack() { + let size = 10 + + for (var i = 0; i < 3; i++) { + for (var j = 0; j < 3; j++) { + let center = Qt.vector3d(j*100, 100*i, 0) + let box = boxComponent.incubateObject(shapeSpawner, { + "position": center, + }) + instancesBoxes.push(box) + } + } + + numSpawns = numSpawns + 1; + } + + function reset() { + // Only run method if previous stack has been created fully + for (var i = 0; i < instancesBoxes.length; i++) + if (!instancesBoxes[i].object) + return + + instancesBoxes.forEach(box => { + box.object.collisionShapes = [] + box.object.destroy() + }) + instancesBoxes = [] + + shapeSpawner.createStack() + } + } + } + + FrameAnimation { + property int frame: 0 + running: true + onTriggered: { + frame = frame + 1; + if (frame % 2 == 0) { + shapeSpawner.reset() + } + } + } + + TestCase { + name: "100 cycles" + when: shapeSpawner.numSpawns > 100 + function triggered() {} + } + +} + diff --git a/tests/baseline/data/DebugDrawSharedShapes.qml b/tests/baseline/data/DebugDrawSharedShapes.qml index a3fd43a..0b9cc3a 100644 --- a/tests/baseline/data/DebugDrawSharedShapes.qml +++ b/tests/baseline/data/DebugDrawSharedShapes.qml @@ -2,7 +2,7 @@ import QtQuick import QtQuick3D import QtQuick3D.Physics -Window { +Rectangle { width: 640 height: 480 visible: true diff --git a/tests/baseline/data/ScaledPosition.qml b/tests/baseline/data/ScaledPosition.qml index 0aa4320..b51dc81 100644 --- a/tests/baseline/data/ScaledPosition.qml +++ b/tests/baseline/data/ScaledPosition.qml @@ -2,6 +2,9 @@ import QtQuick import QtQuick3D import QtQuick3D.Physics +// Two bodies where one is scaled 1.5x*2x and the other 3x +// and they should have the same size. + Rectangle { width: 640 height: 480 @@ -37,6 +40,7 @@ Rectangle { position: Qt.vector3d(0, -100, 0) } Model { + scale: Qt.vector3d(0.99, 0.99, 0.99) // just to avoid z-fight position: Qt.vector3d(0, -100, 0) source: "#Cube" materials: PrincipledMaterial { @@ -54,6 +58,7 @@ Rectangle { position: Qt.vector3d(0, 100, 0) } Model { + scale: Qt.vector3d(0.99, 0.99, 0.99) // just to avoid z-fight position: Qt.vector3d(0, 100, 0) source: "#Cube" materials: PrincipledMaterial { diff --git a/tests/baseline/data/UpdateScaleHeightfield.qml b/tests/baseline/data/UpdateScaleHeightfield.qml index 181b863..5f41353 100644 --- a/tests/baseline/data/UpdateScaleHeightfield.qml +++ b/tests/baseline/data/UpdateScaleHeightfield.qml @@ -13,7 +13,7 @@ Rectangle { } Timer { - interval: 500; running: true; repeat: false + interval: 1; running: true; repeat: false onTriggered: { tablecloth.scale = Qt.vector3d(10, 10, 10) } diff --git a/tools/cooker/CMakeLists.txt b/tools/cooker/CMakeLists.txt index e956e0e..b2bddc2 100644 --- a/tools/cooker/CMakeLists.txt +++ b/tools/cooker/CMakeLists.txt @@ -18,7 +18,7 @@ target_include_directories(${target_name} SYSTEM ../../src/3rdparty/PhysX/include ../../src/3rdparty/PhysX/pxshared/include ) -if (UNIX) +if (UNIX OR MINGW) # Needed for PxPreprocessor.h error if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") qt_internal_extend_target(${target_name} DEFINES _DEBUG) |