Revision control
++ // 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 "qphysxworld_p.h"
++
++ #include "characterkinematic/PxControllerManager.h"
++ #include "extensions/PxDefaultCpuDispatcher.h"
++ #include "foundation/PxFoundation.h"
++ #include "PxPhysics.h"
++ #include "foundation/PxPhysicsVersion.h"
++ #include "PxRigidActor.h"
++ #include "PxScene.h"
++ #include "PxShape.h"
++ #include "PxSimulationEventCallback.h"
++
++ #include "qabstractcollisionshape_p.h"
++ #include "qabstractphysicsnode_p.h"
++ #include "qphysicsutils_p.h"
++ #include "qphysicsworld_p.h"
++ #include "qstaticphysxobjects_p.h"
++ #include "qtriggerbody_p.h"
++
++ QT_BEGIN_NAMESPACE
++
++ // The shape a PhysX shape was built for, or null if it was not built for one.
++ // Only ever compared, since the shape can be gone by the time it is looked at.
++ static QAbstractCollisionShape *frontendShape(const physx::PxShape *shape)
++ {
++ return shape ? static_cast<QAbstractCollisionShape *>(shape->userData) : nullptr;
++ }
++
++ class SimulationEventCallback : public physx::PxSimulationEventCallback
++ {
++ public:
++ SimulationEventCallback(QPhysicsWorld *worldIn) : world(worldIn) {};
++ virtual ~SimulationEventCallback() = default;
++
++ void onTrigger(physx::PxTriggerPair *pairs, physx::PxU32 count) override
++ {
++ // A body one of whose shapes leaves a trigger as another of them enters
++ // it has a lost and a found pair in the same batch, and taking the lost
++ // one first would report the body as having left and entered again. So
++ // the found pair is taken first, and only for the trigger and body that
++ // has both: every other pair is handled in the order PhysX gave it, so
++ // a body leaving one trigger as it enters another is still reported that
++ // way.
++ const auto replacesLostOverlap = [pairs, count](physx::PxU32 i) {
++ if (pairs[i].status != physx::PxPairFlag::eNOTIFY_TOUCH_FOUND)
++ return false;
++ for (physx::PxU32 j = 0; j < count; j++) {
++ if (pairs[j].status == physx::PxPairFlag::eNOTIFY_TOUCH_LOST
++ && pairs[j].triggerActor == pairs[i].triggerActor
++ && pairs[j].otherActor == pairs[i].otherActor) {
++ return true;
++ }
++ }
++ return false;
++ };
++
++ // Those first, then every pair in turn. Registering one of them again
++ // on the second pass does nothing: the body is inside already and the
++ // pair is one of its own already.
++ enum Pass { ReplacedOverlaps, EveryPair };
++ for (const Pass pass : { ReplacedOverlaps, EveryPair }) {
++ for (physx::PxU32 i = 0; i < count; i++) {
++ if (pass == ReplacedOverlaps && !replacesLostOverlap(i))
++ continue;
++
++ // ignore pairs when shapes have been deleted
++ if (pairs[i].flags
++ & (physx::PxTriggerPairFlag::eREMOVED_SHAPE_TRIGGER
++ | physx::PxTriggerPairFlag::eREMOVED_SHAPE_OTHER))
++ continue;
++
++ QTriggerBody *triggerNode =
++ static_cast<QTriggerBody *>(pairs[i].triggerActor->userData);
++
++ QAbstractPhysicsNode *otherNode =
++ static_cast<QAbstractPhysicsNode *>(pairs[i].otherActor->userData);
++
++ if (!triggerNode || !otherNode) {
++ qWarning() << "QtQuick3DPhysics internal error: null pointer in trigger "
++ "collision.";
++ continue;
++ }
++
++ // Asked per pair, since one batch can report the same node in
++ // several pairs and a handler run for one of them can delete a
++ // node a later one points at. Nothing else can: PhysX sends
++ // these from the thread that deletes nodes.
++ if (world->isNodeRemoved(triggerNode) || world->isNodeRemoved(otherNode))
++ continue;
++
++ // Passed on so the trigger can tell one pair of a body from
++ // another: PhysX reports a pair of shapes and a body is several
++ // of those, so one of them stopping overlapping is not the body
++ // leaving.
++ const QTriggerBody::ShapePair shapes { frontendShape(pairs[i].triggerShape),
++ frontendShape(pairs[i].otherShape) };
++
++ // Whether a body is worth following, and what it is owed, is
++ // the trigger's to decide, once per body rather than once per
++ // pair: a body that changes its mind while inside would
++ // otherwise be half followed.
++ if (pairs[i].status == physx::PxPairFlag::eNOTIFY_TOUCH_FOUND)
++ triggerNode->registerCollision(otherNode, shapes);
++ else if (pairs[i].status == physx::PxPairFlag::eNOTIFY_TOUCH_LOST)
++ triggerNode->deregisterCollision(otherNode, shapes);
++ }
++ }
++ }
++
++ void onConstraintBreak(physx::PxConstraintInfo * /*constraints*/,
++ physx::PxU32 /*count*/) override {};
++ void onWake(physx::PxActor ** /*actors*/, physx::PxU32 /*count*/) override {};
++ void onSleep(physx::PxActor ** /*actors*/, physx::PxU32 /*count*/) override {};
++ void onContact(const physx::PxContactPairHeader &pairHeader, const physx::PxContactPair *pairs,
++ physx::PxU32 nbPairs) override
++ {
++ constexpr physx::PxU32 bufferSize = 64;
++ physx::PxContactPairPoint contacts[bufferSize];
++
++ for (physx::PxU32 i = 0; i < nbPairs; i++) {
++ const physx::PxContactPair &contactPair = pairs[i];
++
++ if (contactPair.events & physx::PxPairFlag::eNOTIFY_TOUCH_FOUND) {
++ QAbstractPhysicsNode *trigger =
++ static_cast<QAbstractPhysicsNode *>(pairHeader.actors[0]->userData);
++ QAbstractPhysicsNode *other =
++ static_cast<QAbstractPhysicsNode *>(pairHeader.actors[1]->userData);
++
++ if (!trigger || !other || world->isNodeRemoved(trigger)
++ || world->isNodeRemoved(other) || !trigger->m_backendObject
++ || !other->m_backendObject)
++ continue;
++
++ const bool triggerReceive =
++ trigger->receiveContactReports() && other->sendContactReports();
++ const bool otherReceive =
++ other->receiveContactReports() && trigger->sendContactReports();
++
++ if (!triggerReceive && !otherReceive)
++ continue;
++
++ physx::PxU32 nbContacts = pairs[i].extractContacts(contacts, bufferSize);
++
++ QList<QVector3D> positions;
++ QList<QVector3D> impulses;
++ QList<QVector3D> normals;
++
++ positions.reserve(nbContacts);
++ impulses.reserve(nbContacts);
++ normals.reserve(nbContacts);
++
++ for (physx::PxU32 j = 0; j < nbContacts; j++) {
++ physx::PxVec3 position = contacts[j].position;
++ physx::PxVec3 impulse = contacts[j].impulse;
++ physx::PxVec3 normal = contacts[j].normal;
++
++ positions.push_back(QPhysicsUtils::toQtType(position));
++ impulses.push_back(QPhysicsUtils::toQtType(impulse));
++ normals.push_back(QPhysicsUtils::toQtType(normal));
++ }
++
++ QList<QVector3D> normalsInverted;
++ normalsInverted.reserve(normals.size());
++ for (const QVector3D &v : std::as_const(normals)) {
++ normalsInverted.push_back(QVector3D(-v.x(), -v.y(), -v.z()));
++ }
++
++ if (triggerReceive)
++ world->registerContact(other, trigger, positions, impulses, normals);
++ if (otherReceive)
++ world->registerContact(trigger, other, positions, impulses, normalsInverted);
++ }
++ }
++ };
++ void onAdvance(const physx::PxRigidBody *const * /*bodyBuffer*/,
++ const physx::PxTransform * /*poseBuffer*/,
++ const physx::PxU32 /*count*/) override {};
++
++ private:
++ QPhysicsWorld *world = nullptr;
++ };
++
++ static constexpr bool isBitSet(quint32 value, quint32 position)
++ {
++ Q_ASSERT(position <= 32);
++ return value & (1 << (position));
++ }
++
++ // If any 'id' bit is set in the other mask it means collisions should be ignored, i.e.
++ static bool isFilteredOut(physx::PxFilterData filterData0, physx::PxFilterData filterData1)
++ {
++ // First word is id, second is collision mask
++ const quint32 id0 = filterData0.word0;
++ const quint32 id1 = filterData1.word0;
++ const quint32 mask0 = filterData0.word1;
++ const quint32 mask1 = filterData1.word1;
++
++ return id0 < 32 && id1 < 32 && (isBitSet(mask0, id1) || isBitSet(mask1, id0));
++ }
++
++ static physx::PxFilterFlags
++ contactReportFilterShader(physx::PxFilterObjectAttributes attributes0,
++ physx::PxFilterData filterData0,
++ physx::PxFilterObjectAttributes attributes1,
++ physx::PxFilterData filterData1, physx::PxPairFlags &pairFlags,
++ const void * /*constantBlock*/, physx::PxU32 /*constantBlockSize*/)
++ {
++ if (isFilteredOut(filterData0, filterData1)) {
++ // We return a 'suppress' since that will still re-evaluate when filter data is changed.
++ return physx::PxFilterFlag::eSUPPRESS;
++ }
++
++ // A trigger shape does not collide, it only reports what overlaps it, so
++ // there is nothing to solve and nothing for a continuous sweep to do. The
++ // scene has CCD enabled unconditionally (see below), and PhysX warns for
++ // every trigger pair that asks for a CCD contact, so ask only for what a
++ // trigger actually needs.
++ if (physx::PxFilterObjectIsTrigger(attributes0)
++ || physx::PxFilterObjectIsTrigger(attributes1)) {
++ pairFlags = physx::PxPairFlag::eTRIGGER_DEFAULT;
++ return physx::PxFilterFlag::eDEFAULT;
++ }
++
++ // Makes objects collide
++ const auto defaultCollisonFlags =
++ physx::PxPairFlag::eSOLVE_CONTACT |
++ physx::PxPairFlag::eDETECT_DISCRETE_CONTACT |
++ physx::PxPairFlag::eDETECT_CCD_CONTACT; // will be ignored by physX engine if the ccd is disabled
++
++ // For trigger body detection
++ const auto notifyTouchFlags =
++ physx::PxPairFlag::eNOTIFY_TOUCH_FOUND | physx::PxPairFlag::eNOTIFY_TOUCH_LOST;
++
++ // For contact detection
++ const auto notifyContactFlags = physx::PxPairFlag::eNOTIFY_CONTACT_POINTS;
++
++ pairFlags = defaultCollisonFlags | notifyTouchFlags | notifyContactFlags;
++ return physx::PxFilterFlag::eDEFAULT;
++ }
++
++ #define PHYSX_RELEASE(x) \
++ if (x != nullptr) { \
++ x->release(); \
++ x = nullptr; \
++ }
++
++ void QPhysXWorld::createWorld()
++ {
++ auto &s_physx = StaticPhysXObjects::getReference();
++ s_physx.foundationRefCount++;
++
++ if (s_physx.foundationCreated)
++ return;
++
++ s_physx.foundation = PxCreateFoundation(
++ PX_PHYSICS_VERSION, s_physx.defaultAllocatorCallback, s_physx.defaultErrorCallback);
++ if (!s_physx.foundation)
++ qFatal("PxCreateFoundation failed!");
++
++ s_physx.foundationCreated = true;
++ }
++
++ void QPhysXWorld::deleteWorld()
++ {
++ auto &s_physx = StaticPhysXObjects::getReference();
++ s_physx.foundationRefCount--;
++ if (s_physx.foundationRefCount == 0) {
++ PHYSX_RELEASE(controllerManager);
++ PHYSX_RELEASE(scene);
++ PHYSX_RELEASE(s_physx.dispatcher);
++ // Every node releases its material when it is cleaned up, which happens before the
++ // last world is deleted, so nothing should be left to release here
++ Q_ASSERT(s_physx.materials.isEmpty());
++ s_physx.materials.clear();
++ PHYSX_RELEASE(s_physx.physics);
++ PHYSX_RELEASE(s_physx.foundation);
++
++ delete callback;
++ callback = nullptr;
++ s_physx.foundationCreated = false;
++ s_physx.physicsCreated = false;
++ } else {
++ delete callback;
++ callback = nullptr;
++ PHYSX_RELEASE(controllerManager);
++ PHYSX_RELEASE(scene);
++ }
++ }
++
++ void QPhysXWorld::createScene(float typicalLength, float typicalSpeed, const QVector3D &gravity,
++ QPhysicsWorld *physicsWorld, unsigned int numThreads)
++ {
++ if (scene) {
++ qWarning() << "Scene already created";
++ return;
++ }
++
++ physx::PxTolerancesScale scale;
++ scale.length = typicalLength;
++ scale.speed = typicalSpeed;
++
++ auto &s_physx = StaticPhysXObjects::getReference();
++
++ if (!s_physx.physicsCreated) {
++ constexpr bool recordMemoryAllocations = true;
++ s_physx.physics = PxCreatePhysics(PX_PHYSICS_VERSION, *s_physx.foundation, scale,
++ recordMemoryAllocations);
++ if (!s_physx.physics)
++ qFatal("PxCreatePhysics failed!");
++
++ s_physx.dispatcher = physx::PxDefaultCpuDispatcherCreate(numThreads);
++ s_physx.physicsCreated = true;
++ }
++
++ callback = new SimulationEventCallback(physicsWorld);
++
++ physx::PxSceneDesc sceneDesc(scale);
++ sceneDesc.gravity = QPhysicsUtils::toPhysXType(gravity);
++ sceneDesc.cpuDispatcher = s_physx.dispatcher;
++
++ sceneDesc.filterShader = contactReportFilterShader;
++ // CCD is always enabled at the scene level. Since PhysX's CCD pass is cheap enough
++ // (see PxsCCDContext::updateCCD) when there are no CCD-enabled bodies in the scene,
++ // it makes the code simpler.
++ sceneDesc.flags |= physx::PxSceneFlag::eENABLE_CCD;
++ sceneDesc.solverType = physx::PxSolverType::eTGS;
++ sceneDesc.simulationEventCallback = callback;
++
++ if (physicsWorld->reportKinematicKinematicCollisions())
++ sceneDesc.kineKineFilteringMode = physx::PxPairFilteringMode::eKEEP;
++ if (physicsWorld->reportStaticKinematicCollisions())
++ sceneDesc.staticKineFilteringMode = physx::PxPairFilteringMode::eKEEP;
++
++ switch (physicsWorld->staticQueryStructure()) {
++ case QPhysicsWorld::QueryStructure::NoStructure: {
++ Q_ASSERT(false && "Unreachable: setStaticQueryStructure() rejects NoStructure");
++ break;
++ }
++
++ case QPhysicsWorld::QueryStructure::StaticTree: {
++ sceneDesc.staticStructure = physx::PxPruningStructureType::eSTATIC_AABB_TREE;
++ break;
++ }
++
++ case QPhysicsWorld::QueryStructure::DynamicTree: {
++ sceneDesc.staticStructure = physx::PxPruningStructureType::eDYNAMIC_AABB_TREE;
++ break;
++ }
++
++ }
++
++ switch (physicsWorld->dynamicQueryStructure()) {
++ case QPhysicsWorld::QueryStructure::NoStructure: {
++ sceneDesc.dynamicStructure = physx::PxPruningStructureType::eNONE;
++ break;
++ }
++
++ case QPhysicsWorld::QueryStructure::StaticTree: {
++ sceneDesc.dynamicStructure = physx::PxPruningStructureType::eSTATIC_AABB_TREE;
++ break;
++ }
++
++ case QPhysicsWorld::QueryStructure::DynamicTree: {
++ sceneDesc.dynamicStructure = physx::PxPruningStructureType::eDYNAMIC_AABB_TREE;
++ break;
++ }
++
++ }
++
++ scene = s_physx.physics->createScene(sceneDesc);
++ }
++
++ QT_END_NAMESPACE