| 1 | // Copyright (C) 2023 The Qt Company Ltd. | 
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only | 
| 3 |  | 
| 4 | #include "qphysxworld_p.h" | 
| 5 |  | 
| 6 | #include "characterkinematic/PxControllerManager.h" | 
| 7 | #include "cooking/PxCooking.h" | 
| 8 | #include "extensions/PxDefaultCpuDispatcher.h" | 
| 9 | #include "pvd/PxPvdTransport.h" | 
| 10 | #include "PxFoundation.h" | 
| 11 | #include "PxPhysics.h" | 
| 12 | #include "PxPhysicsVersion.h" | 
| 13 | #include "PxRigidActor.h" | 
| 14 | #include "PxScene.h" | 
| 15 | #include "PxSimulationEventCallback.h" | 
| 16 |  | 
| 17 | #include "qabstractphysicsnode_p.h" | 
| 18 | #include "qphysicsutils_p.h" | 
| 19 | #include "qphysicsworld_p.h" | 
| 20 | #include "qstaticphysxobjects_p.h" | 
| 21 | #include "qtriggerbody_p.h" | 
| 22 |  | 
| 23 | QT_BEGIN_NAMESPACE | 
| 24 |  | 
| 25 | class SimulationEventCallback : public physx::PxSimulationEventCallback | 
| 26 | { | 
| 27 | public: | 
| 28 |     SimulationEventCallback(QPhysicsWorld *worldIn) : world(worldIn) {}; | 
| 29 |     virtual ~SimulationEventCallback() = default; | 
| 30 |  | 
| 31 |     void onTrigger(physx::PxTriggerPair *pairs, physx::PxU32 count) override | 
| 32 |     { | 
| 33 |         QMutexLocker locker(&world->m_removedPhysicsNodesMutex); | 
| 34 |  | 
| 35 |         for (physx::PxU32 i = 0; i < count; i++) { | 
| 36 |             // ignore pairs when shapes have been deleted | 
| 37 |             if (pairs[i].flags | 
| 38 |                 & (physx::PxTriggerPairFlag::eREMOVED_SHAPE_TRIGGER | 
| 39 |                    | physx::PxTriggerPairFlag::eREMOVED_SHAPE_OTHER)) | 
| 40 |                 continue; | 
| 41 |  | 
| 42 |             QTriggerBody *triggerNode = | 
| 43 |                     static_cast<QTriggerBody *>(pairs[i].triggerActor->userData); | 
| 44 |  | 
| 45 |             QAbstractPhysicsNode *otherNode = | 
| 46 |                     static_cast<QAbstractPhysicsNode *>(pairs[i].otherActor->userData); | 
| 47 |  | 
| 48 |             if (!triggerNode || !otherNode) { | 
| 49 |                 qWarning() << "QtQuick3DPhysics internal error: null pointer in trigger collision." ; | 
| 50 |                 continue; | 
| 51 |             } | 
| 52 |  | 
| 53 |             if (world->isNodeRemoved(object: triggerNode) || world->isNodeRemoved(object: otherNode)) | 
| 54 |                 continue; | 
| 55 |  | 
| 56 |             if (pairs->status == physx::PxPairFlag::eNOTIFY_TOUCH_FOUND) { | 
| 57 |                 if (otherNode->sendTriggerReports()) { | 
| 58 |                     triggerNode->registerCollision(collision: otherNode); | 
| 59 |                 } | 
| 60 |                 if (otherNode->receiveTriggerReports()) { | 
| 61 |                     emit otherNode->enteredTriggerBody(body: triggerNode); | 
| 62 |                 } | 
| 63 |             } else if (pairs->status == physx::PxPairFlag::eNOTIFY_TOUCH_LOST) { | 
| 64 |                 if (otherNode->sendTriggerReports()) { | 
| 65 |                     triggerNode->deregisterCollision(collision: otherNode); | 
| 66 |                 } | 
| 67 |                 if (otherNode->receiveTriggerReports()) { | 
| 68 |                     emit otherNode->exitedTriggerBody(body: triggerNode); | 
| 69 |                 } | 
| 70 |             } | 
| 71 |         } | 
| 72 |     } | 
| 73 |  | 
| 74 |     void onConstraintBreak(physx::PxConstraintInfo * /*constraints*/, | 
| 75 |                            physx::PxU32 /*count*/) override {}; | 
| 76 |     void onWake(physx::PxActor ** /*actors*/, physx::PxU32 /*count*/) override {}; | 
| 77 |     void onSleep(physx::PxActor ** /*actors*/, physx::PxU32 /*count*/) override {}; | 
| 78 |     void (const physx::PxContactPairHeader &, const physx::PxContactPair *pairs, | 
| 79 |                    physx::PxU32 nbPairs) override | 
| 80 |     { | 
| 81 |         QMutexLocker locker(&world->m_removedPhysicsNodesMutex); | 
| 82 |         constexpr physx::PxU32 bufferSize = 64; | 
| 83 |         physx::PxContactPairPoint contacts[bufferSize]; | 
| 84 |  | 
| 85 |         for (physx::PxU32 i = 0; i < nbPairs; i++) { | 
| 86 |             const physx::PxContactPair &contactPair = pairs[i]; | 
| 87 |  | 
| 88 |             if (contactPair.events & physx::PxPairFlag::eNOTIFY_TOUCH_FOUND) { | 
| 89 |                 QAbstractPhysicsNode *trigger = | 
| 90 |                         static_cast<QAbstractPhysicsNode *>(pairHeader.actors[0]->userData); | 
| 91 |                 QAbstractPhysicsNode *other = | 
| 92 |                         static_cast<QAbstractPhysicsNode *>(pairHeader.actors[1]->userData); | 
| 93 |  | 
| 94 |                 if (!trigger || !other || world->isNodeRemoved(object: trigger) | 
| 95 |                     || world->isNodeRemoved(object: other) || !trigger->m_backendObject | 
| 96 |                     || !other->m_backendObject) | 
| 97 |                     continue; | 
| 98 |  | 
| 99 |                 const bool triggerReceive = | 
| 100 |                         trigger->receiveContactReports() && other->sendContactReports(); | 
| 101 |                 const bool otherReceive = | 
| 102 |                         other->receiveContactReports() && trigger->sendContactReports(); | 
| 103 |  | 
| 104 |                 if (!triggerReceive && !otherReceive) | 
| 105 |                     continue; | 
| 106 |  | 
| 107 |                 physx::PxU32 nbContacts = pairs[i].extractContacts(userBuffer: contacts, bufferSize); | 
| 108 |  | 
| 109 |                 QList<QVector3D> positions; | 
| 110 |                 QList<QVector3D> impulses; | 
| 111 |                 QList<QVector3D> normals; | 
| 112 |  | 
| 113 |                 positions.reserve(asize: nbContacts); | 
| 114 |                 impulses.reserve(asize: nbContacts); | 
| 115 |                 normals.reserve(asize: nbContacts); | 
| 116 |  | 
| 117 |                 for (physx::PxU32 j = 0; j < nbContacts; j++) { | 
| 118 |                     physx::PxVec3 position = contacts[j].position; | 
| 119 |                     physx::PxVec3 impulse = contacts[j].impulse; | 
| 120 |                     physx::PxVec3 normal = contacts[j].normal; | 
| 121 |  | 
| 122 |                     positions.push_back(t: QPhysicsUtils::toQtType(vec: position)); | 
| 123 |                     impulses.push_back(t: QPhysicsUtils::toQtType(vec: impulse)); | 
| 124 |                     normals.push_back(t: QPhysicsUtils::toQtType(vec: normal)); | 
| 125 |                 } | 
| 126 |  | 
| 127 |                 QList<QVector3D> normalsInverted; | 
| 128 |                 normalsInverted.reserve(asize: normals.size()); | 
| 129 |                 for (const QVector3D &v : normals) { | 
| 130 |                     normalsInverted.push_back(t: QVector3D(-v.x(), -v.y(), -v.z())); | 
| 131 |                 } | 
| 132 |  | 
| 133 |                 if (triggerReceive) | 
| 134 |                     world->registerContact(sender: other, receiver: trigger, positions, impulses, normals); | 
| 135 |                 if (otherReceive) | 
| 136 |                     world->registerContact(sender: trigger, receiver: other, positions, impulses, normals: normalsInverted); | 
| 137 |             } | 
| 138 |         } | 
| 139 |     }; | 
| 140 |     void onAdvance(const physx::PxRigidBody *const * /*bodyBuffer*/, | 
| 141 |                    const physx::PxTransform * /*poseBuffer*/, | 
| 142 |                    const physx::PxU32 /*count*/) override {}; | 
| 143 |  | 
| 144 | private: | 
| 145 |     QPhysicsWorld *world = nullptr; | 
| 146 | }; | 
| 147 |  | 
| 148 | static constexpr bool isBitSet(quint32 value, quint32 position) | 
| 149 | { | 
| 150 |     Q_ASSERT(position <= 32); | 
| 151 |     return value & (1 << (position)); | 
| 152 | } | 
| 153 |  | 
| 154 | static physx::PxFilterFlags | 
| 155 | contactReportFilterShader(physx::PxFilterObjectAttributes /*attributes0*/, | 
| 156 |                           physx::PxFilterData filterData0, | 
| 157 |                           physx::PxFilterObjectAttributes /*attributes1*/, | 
| 158 |                           physx::PxFilterData filterData1, physx::PxPairFlags &pairFlags, | 
| 159 |                           const void * /*constantBlock*/, physx::PxU32 /*constantBlockSize*/) | 
| 160 | { | 
| 161 |     // First word is id, second is collision mask | 
| 162 |     const quint32 id0 = filterData0.word0; | 
| 163 |     const quint32 id1 = filterData1.word0; | 
| 164 |     const quint32 mask0 = filterData0.word1; | 
| 165 |     const quint32 mask1 = filterData1.word1; | 
| 166 |  | 
| 167 |     // If any 'id' bit is set in the other mask it means collisions should be ignored | 
| 168 |     if (id0 < 32 && id1 < 32 && (isBitSet(value: mask0, position: id1) || isBitSet(value: mask1, position: id0))) { | 
| 169 |         // We return a 'suppress' since that will still re-evaluate when filter data is changed. | 
| 170 |         return physx::PxFilterFlag::eSUPPRESS; | 
| 171 |     } | 
| 172 |  | 
| 173 |     // Makes objects collide | 
| 174 |     const auto defaultCollisonFlags = | 
| 175 |             physx::PxPairFlag::eSOLVE_CONTACT | physx::PxPairFlag::eDETECT_DISCRETE_CONTACT; | 
| 176 |  | 
| 177 |     // For trigger body detection | 
| 178 |     const auto notifyTouchFlags = | 
| 179 |             physx::PxPairFlag::eNOTIFY_TOUCH_FOUND | physx::PxPairFlag::eNOTIFY_TOUCH_LOST; | 
| 180 |  | 
| 181 |     // For contact detection | 
| 182 |     const auto notifyContactFlags = physx::PxPairFlag::eNOTIFY_CONTACT_POINTS; | 
| 183 |  | 
| 184 |     pairFlags = defaultCollisonFlags | notifyTouchFlags | notifyContactFlags; | 
| 185 |     return physx::PxFilterFlag::eDEFAULT; | 
| 186 | } | 
| 187 |  | 
| 188 | static physx::PxFilterFlags | 
| 189 | contactReportFilterShaderCCD(physx::PxFilterObjectAttributes /*attributes0*/, | 
| 190 |                              physx::PxFilterData /*filterData0*/, | 
| 191 |                              physx::PxFilterObjectAttributes /*attributes1*/, | 
| 192 |                              physx::PxFilterData /*filterData1*/, physx::PxPairFlags &pairFlags, | 
| 193 |                              const void * /*constantBlock*/, physx::PxU32 /*constantBlockSize*/) | 
| 194 | { | 
| 195 |     // Makes objects collide | 
| 196 |     const auto defaultCollisonFlags = physx::PxPairFlag::eSOLVE_CONTACT | 
| 197 |             | physx::PxPairFlag::eDETECT_DISCRETE_CONTACT | physx::PxPairFlag::eDETECT_CCD_CONTACT; | 
| 198 |  | 
| 199 |     // For trigger body detection | 
| 200 |     const auto notifyTouchFlags = | 
| 201 |             physx::PxPairFlag::eNOTIFY_TOUCH_FOUND | physx::PxPairFlag::eNOTIFY_TOUCH_LOST; | 
| 202 |  | 
| 203 |     // For contact detection | 
| 204 |     const auto notifyContactFlags = physx::PxPairFlag::eNOTIFY_CONTACT_POINTS; | 
| 205 |  | 
| 206 |     pairFlags = defaultCollisonFlags | notifyTouchFlags | notifyContactFlags; | 
| 207 |     return physx::PxFilterFlag::eDEFAULT; | 
| 208 | } | 
| 209 |  | 
| 210 | #define PHYSX_RELEASE(x)                                                                           \ | 
| 211 |     if (x != nullptr) {                                                                            \ | 
| 212 |         x->release();                                                                              \ | 
| 213 |         x = nullptr;                                                                               \ | 
| 214 |     } | 
| 215 |  | 
| 216 | void QPhysXWorld::createWorld() | 
| 217 | { | 
| 218 |     auto &s_physx = StaticPhysXObjects::getReference(); | 
| 219 |     s_physx.foundationRefCount++; | 
| 220 |  | 
| 221 |     if (s_physx.foundationCreated) | 
| 222 |         return; | 
| 223 |  | 
| 224 |     s_physx.foundation = PxCreateFoundation( | 
| 225 |             PX_PHYSICS_VERSION, allocator&: s_physx.defaultAllocatorCallback, errorCallback&: s_physx.defaultErrorCallback); | 
| 226 |     if (!s_physx.foundation) | 
| 227 |         qFatal(msg: "PxCreateFoundation failed!" ); | 
| 228 |  | 
| 229 |     s_physx.foundationCreated = true; | 
| 230 |  | 
| 231 | #if PHYSX_ENABLE_PVD | 
| 232 |     s_physx.pvd = PxCreatePvd(*m_physx->foundation); | 
| 233 |     s_physx.transport = physx::PxDefaultPvdSocketTransportCreate("qt" , 5425, 10); | 
| 234 |     s_physx.pvd->connect(*m_physx->transport, physx::PxPvdInstrumentationFlag::eALL); | 
| 235 | #endif | 
| 236 |  | 
| 237 |     // FIXME: does the tolerance matter? | 
| 238 |     s_physx.cooking = PxCreateCooking(PX_PHYSICS_VERSION, foundation&: *s_physx.foundation, | 
| 239 |                                       params: physx::PxCookingParams(physx::PxTolerancesScale())); | 
| 240 |  | 
| 241 | } | 
| 242 |  | 
| 243 | void QPhysXWorld::deleteWorld() | 
| 244 | { | 
| 245 |     auto &s_physx = StaticPhysXObjects::getReference(); | 
| 246 |     s_physx.foundationRefCount--; | 
| 247 |     if (s_physx.foundationRefCount == 0) { | 
| 248 |         PHYSX_RELEASE(controllerManager); | 
| 249 |         PHYSX_RELEASE(scene); | 
| 250 |         PHYSX_RELEASE(s_physx.dispatcher); | 
| 251 |         PHYSX_RELEASE(s_physx.cooking); | 
| 252 |         PHYSX_RELEASE(s_physx.transport); | 
| 253 |         PHYSX_RELEASE(s_physx.pvd); | 
| 254 |         PHYSX_RELEASE(s_physx.physics); | 
| 255 |         PHYSX_RELEASE(s_physx.foundation); | 
| 256 |  | 
| 257 |         delete callback; | 
| 258 |         callback = nullptr; | 
| 259 |         s_physx.foundationCreated = false; | 
| 260 |         s_physx.physicsCreated = false; | 
| 261 |     } else { | 
| 262 |         delete callback; | 
| 263 |         callback = nullptr; | 
| 264 |         PHYSX_RELEASE(controllerManager); | 
| 265 |         PHYSX_RELEASE(scene); | 
| 266 |     } | 
| 267 | } | 
| 268 |  | 
| 269 | void QPhysXWorld::createScene(float typicalLength, float typicalSpeed, const QVector3D &gravity, | 
| 270 |                               bool enableCCD, QPhysicsWorld *physicsWorld, unsigned int numThreads) | 
| 271 | { | 
| 272 |     if (scene) { | 
| 273 |         qWarning() << "Scene already created" ; | 
| 274 |         return; | 
| 275 |     } | 
| 276 |  | 
| 277 |     physx::PxTolerancesScale scale; | 
| 278 |     scale.length = typicalLength; | 
| 279 |     scale.speed = typicalSpeed; | 
| 280 |  | 
| 281 |     auto &s_physx = StaticPhysXObjects::getReference(); | 
| 282 |  | 
| 283 |     if (!s_physx.physicsCreated) { | 
| 284 |         constexpr bool recordMemoryAllocations = true; | 
| 285 |         s_physx.physics = PxCreatePhysics(PX_PHYSICS_VERSION, foundation&: *s_physx.foundation, scale, | 
| 286 |                                           trackOutstandingAllocations: recordMemoryAllocations, pvd: s_physx.pvd); | 
| 287 |         if (!s_physx.physics) | 
| 288 |             qFatal(msg: "PxCreatePhysics failed!" ); | 
| 289 |  | 
| 290 |         s_physx.dispatcher = physx::PxDefaultCpuDispatcherCreate(numThreads); | 
| 291 |         s_physx.physicsCreated = true; | 
| 292 |     } | 
| 293 |  | 
| 294 |     callback = new SimulationEventCallback(physicsWorld); | 
| 295 |  | 
| 296 |     physx::PxSceneDesc sceneDesc(scale); | 
| 297 |     sceneDesc.gravity = QPhysicsUtils::toPhysXType(qvec: gravity); | 
| 298 |     sceneDesc.cpuDispatcher = s_physx.dispatcher; | 
| 299 |  | 
| 300 |     if (enableCCD) { | 
| 301 |         sceneDesc.filterShader = contactReportFilterShaderCCD; | 
| 302 |         sceneDesc.flags |= physx::PxSceneFlag::eENABLE_CCD; | 
| 303 |     } else { | 
| 304 |         sceneDesc.filterShader = contactReportFilterShader; | 
| 305 |     } | 
| 306 |     sceneDesc.solverType = physx::PxSolverType::eTGS; | 
| 307 |     sceneDesc.simulationEventCallback = callback; | 
| 308 |  | 
| 309 |     if (physicsWorld->reportKinematicKinematicCollisions()) | 
| 310 |         sceneDesc.kineKineFilteringMode = physx::PxPairFilteringMode::eKEEP; | 
| 311 |     if (physicsWorld->reportStaticKinematicCollisions()) | 
| 312 |         sceneDesc.staticKineFilteringMode = physx::PxPairFilteringMode::eKEEP; | 
| 313 |  | 
| 314 |     scene = s_physx.physics->createScene(sceneDesc); | 
| 315 | } | 
| 316 |  | 
| 317 | QT_END_NAMESPACE | 
| 318 |  |