Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qphysxworld.cpp
Go to the documentation of this file.
1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
6
7#include "characterkinematic/PxControllerManager.h"
8#include "cooking/PxCooking.h"
9#include "extensions/PxDefaultCpuDispatcher.h"
10#include "pvd/PxPvdTransport.h"
11#include "PxFoundation.h"
12#include "PxPhysics.h"
13#include "PxPhysicsVersion.h"
14#include "PxRigidActor.h"
15#include "PxScene.h"
16#include "PxSimulationEventCallback.h"
17
22#include "qtriggerbody_p.h"
23
25
27{
28public:
29 SimulationEventCallback(QPhysicsWorld *worldIn) : world(worldIn) {};
30 virtual ~SimulationEventCallback() = default;
31
32 void onTrigger(physx::PxTriggerPair *pairs, physx::PxU32 count) override
33 {
34 QMutexLocker locker(&world->m_removedPhysicsNodesMutex);
35
36 for (physx::PxU32 i = 0; i < count; i++) {
37 // ignore pairs when shapes have been deleted
38 if (pairs[i].flags
39 & (physx::PxTriggerPairFlag::eREMOVED_SHAPE_TRIGGER
40 | physx::PxTriggerPairFlag::eREMOVED_SHAPE_OTHER))
41 continue;
42
43 QTriggerBody *triggerNode =
44 static_cast<QTriggerBody *>(pairs[i].triggerActor->userData);
45
46 QAbstractPhysicsNode *otherNode =
47 static_cast<QAbstractPhysicsNode *>(pairs[i].otherActor->userData);
48
49 if (!triggerNode || !otherNode) {
50 qWarning() << "QtQuick3DPhysics internal error: null pointer in trigger collision.";
51 continue;
52 }
53
54 if (world->isNodeRemoved(triggerNode) || world->isNodeRemoved(otherNode))
55 continue;
56
57 const auto status = pairs[i].status;
58
59 if (status == physx::PxPairFlag::eNOTIFY_TOUCH_FOUND) {
60 if (otherNode->sendTriggerReports()) {
61 triggerNode->registerCollision(otherNode);
62 }
63 if (otherNode->receiveTriggerReports()) {
64 emit otherNode->enteredTriggerBody(triggerNode);
65 }
66 } else if (status == physx::PxPairFlag::eNOTIFY_TOUCH_LOST) {
67 if (otherNode->sendTriggerReports()) {
68 triggerNode->deregisterCollision(otherNode);
69 }
70 if (otherNode->receiveTriggerReports()) {
71 emit otherNode->exitedTriggerBody(triggerNode);
72 }
73 }
74 }
75 }
76
77 void onConstraintBreak(physx::PxConstraintInfo * /*constraints*/,
78 physx::PxU32 /*count*/) override {};
79 void onWake(physx::PxActor ** /*actors*/, physx::PxU32 /*count*/) override {};
80 void onSleep(physx::PxActor ** /*actors*/, physx::PxU32 /*count*/) override {};
81 void onContact(const physx::PxContactPairHeader &pairHeader, const physx::PxContactPair *pairs,
82 physx::PxU32 nbPairs) override
83 {
84 QMutexLocker locker(&world->m_removedPhysicsNodesMutex);
85 constexpr physx::PxU32 bufferSize = 64;
86 physx::PxContactPairPoint contacts[bufferSize];
87
88 for (physx::PxU32 i = 0; i < nbPairs; i++) {
89 const physx::PxContactPair &contactPair = pairs[i];
90
91 if (contactPair.events & physx::PxPairFlag::eNOTIFY_TOUCH_FOUND) {
92 QAbstractPhysicsNode *trigger =
93 static_cast<QAbstractPhysicsNode *>(pairHeader.actors[0]->userData);
94 QAbstractPhysicsNode *other =
95 static_cast<QAbstractPhysicsNode *>(pairHeader.actors[1]->userData);
96
97 if (!trigger || !other || world->isNodeRemoved(trigger)
98 || world->isNodeRemoved(other) || !trigger->m_backendObject
99 || !other->m_backendObject)
100 continue;
101
102 const bool triggerReceive =
103 trigger->receiveContactReports() && other->sendContactReports();
104 const bool otherReceive =
105 other->receiveContactReports() && trigger->sendContactReports();
106
107 if (!triggerReceive && !otherReceive)
108 continue;
109
110 physx::PxU32 nbContacts = pairs[i].extractContacts(contacts, bufferSize);
111
112 QList<QVector3D> positions;
113 QList<QVector3D> impulses;
114 QList<QVector3D> normals;
115
116 positions.reserve(nbContacts);
117 impulses.reserve(nbContacts);
118 normals.reserve(nbContacts);
119
120 for (physx::PxU32 j = 0; j < nbContacts; j++) {
121 physx::PxVec3 position = contacts[j].position;
122 physx::PxVec3 impulse = contacts[j].impulse;
123 physx::PxVec3 normal = contacts[j].normal;
124
125 positions.push_back(QPhysicsUtils::toQtType(position));
126 impulses.push_back(QPhysicsUtils::toQtType(impulse));
127 normals.push_back(QPhysicsUtils::toQtType(normal));
128 }
129
130 QList<QVector3D> normalsInverted;
131 normalsInverted.reserve(normals.size());
132 for (const QVector3D &v : std::as_const(normals)) {
133 normalsInverted.push_back(QVector3D(-v.x(), -v.y(), -v.z()));
134 }
135
136 if (triggerReceive)
137 world->registerContact(other, trigger, positions, impulses, normals);
138 if (otherReceive)
139 world->registerContact(trigger, other, positions, impulses, normalsInverted);
140 }
141 }
142 };
143 void onAdvance(const physx::PxRigidBody *const * /*bodyBuffer*/,
144 const physx::PxTransform * /*poseBuffer*/,
145 const physx::PxU32 /*count*/) override {};
146
147private:
148 QPhysicsWorld *world = nullptr;
149};
150
151static constexpr bool isBitSet(quint32 value, quint32 position)
152{
153 Q_ASSERT(position <= 32);
154 return value & (1 << (position));
155}
156
157// If any 'id' bit is set in the other mask it means collisions should be ignored, i.e.
158static bool isFilteredOut(physx::PxFilterData filterData0, physx::PxFilterData filterData1)
159{
160 // First word is id, second is collision mask
161 const quint32 id0 = filterData0.word0;
162 const quint32 id1 = filterData1.word0;
163 const quint32 mask0 = filterData0.word1;
164 const quint32 mask1 = filterData1.word1;
165
166 return id0 < 32 && id1 < 32 && (isBitSet(mask0, id1) || isBitSet(mask1, id0));
167}
168
169static physx::PxFilterFlags
170contactReportFilterShader(physx::PxFilterObjectAttributes attributes0,
171 physx::PxFilterData filterData0,
172 physx::PxFilterObjectAttributes attributes1,
173 physx::PxFilterData filterData1, physx::PxPairFlags &pairFlags,
174 const void * /*constantBlock*/, physx::PxU32 /*constantBlockSize*/)
175{
176 if (isFilteredOut(filterData0, filterData1)) {
177 // We return a 'suppress' since that will still re-evaluate when filter data is changed.
178 return physx::PxFilterFlag::eSUPPRESS;
179 }
180
181 // A trigger shape does not collide, it only reports what overlaps it, so
182 // there is nothing to solve and nothing for a continuous sweep to do. The
183 // scene has CCD enabled unconditionally (see below), and PhysX warns for
184 // every trigger pair that asks for a CCD contact, so ask only for what a
185 // trigger actually needs.
186 if (physx::PxFilterObjectIsTrigger(attributes0)
187 || physx::PxFilterObjectIsTrigger(attributes1)) {
188 pairFlags = physx::PxPairFlag::eTRIGGER_DEFAULT;
189 return physx::PxFilterFlag::eDEFAULT;
190 }
191
192 // Makes objects collide
193 const auto defaultCollisonFlags =
194 physx::PxPairFlag::eSOLVE_CONTACT |
195 physx::PxPairFlag::eDETECT_DISCRETE_CONTACT |
196 physx::PxPairFlag::eDETECT_CCD_CONTACT; // will be ignored by physX engine if the ccd is disabled
197
198 // For trigger body detection
199 const auto notifyTouchFlags =
200 physx::PxPairFlag::eNOTIFY_TOUCH_FOUND | physx::PxPairFlag::eNOTIFY_TOUCH_LOST;
201
202 // For contact detection
203 const auto notifyContactFlags = physx::PxPairFlag::eNOTIFY_CONTACT_POINTS;
204
205 pairFlags = defaultCollisonFlags | notifyTouchFlags | notifyContactFlags;
206 return physx::PxFilterFlag::eDEFAULT;
207}
208
209#define PHYSX_RELEASE(x)
210 if (x != nullptr) {
211 x->release();
212 x = nullptr;
213 }
214
216{
218 s_physx.foundationRefCount++;
219
220 if (s_physx.foundationCreated)
221 return;
222
223 s_physx.foundation = PxCreateFoundation(
224 PX_PHYSICS_VERSION, s_physx.defaultAllocatorCallback, s_physx.defaultErrorCallback);
225 if (!s_physx.foundation)
226 qFatal("PxCreateFoundation failed!");
227
228 s_physx.foundationCreated = true;
229
230#if PHYSX_ENABLE_PVD
231 s_physx.pvd = PxCreatePvd(*m_physx->foundation);
232 s_physx.transport = physx::PxDefaultPvdSocketTransportCreate("qt", 5425, 10);
233 s_physx.pvd->connect(*m_physx->transport, physx::PxPvdInstrumentationFlag::eALL);
234#endif
235
236 // FIXME: does the tolerance matter?
237 s_physx.cooking = PxCreateCooking(PX_PHYSICS_VERSION, *s_physx.foundation,
238 physx::PxCookingParams(physx::PxTolerancesScale()));
239
240}
241
243{
245 s_physx.foundationRefCount--;
246 if (s_physx.foundationRefCount == 0) {
250 PHYSX_RELEASE(s_physx.cooking);
252 PHYSX_RELEASE(s_physx.pvd);
253 // Every node releases its material when it is cleaned up, which happens before the
254 // last world is deleted, so nothing should be left to release here
255 Q_ASSERT(s_physx.materials.isEmpty());
256 s_physx.materials.clear();
257 PHYSX_RELEASE(s_physx.physics);
259
260 delete callback;
261 callback = nullptr;
262 s_physx.foundationCreated = false;
263 s_physx.physicsCreated = false;
264 } else {
265 delete callback;
266 callback = nullptr;
269 }
270}
271
272void QPhysXWorld::createScene(float typicalLength, float typicalSpeed, const QVector3D &gravity,
273 QPhysicsWorld *physicsWorld, unsigned int numThreads)
274{
275 if (scene) {
276 qWarning() << "Scene already created";
277 return;
278 }
279
280 physx::PxTolerancesScale scale;
281 scale.length = typicalLength;
282 scale.speed = typicalSpeed;
283
285
286 if (!s_physx.physicsCreated) {
287 constexpr bool recordMemoryAllocations = true;
288 s_physx.physics = PxCreatePhysics(PX_PHYSICS_VERSION, *s_physx.foundation, scale,
289 recordMemoryAllocations, s_physx.pvd);
290 if (!s_physx.physics)
291 qFatal("PxCreatePhysics failed!");
292
293 s_physx.dispatcher = physx::PxDefaultCpuDispatcherCreate(numThreads);
294 s_physx.physicsCreated = true;
295 }
296
297 callback = new SimulationEventCallback(physicsWorld);
298
299 physx::PxSceneDesc sceneDesc(scale);
300 sceneDesc.gravity = QPhysicsUtils::toPhysXType(gravity);
301 sceneDesc.cpuDispatcher = s_physx.dispatcher;
302
303 sceneDesc.filterShader = contactReportFilterShader;
304 // CCD is always enabled at the scene level. Since PhysX's CCD pass is cheap enough
305 // (see PxsCCDContext::updateCCD) when there are no CCD-enabled bodies in the scene,
306 // it makes the code simpler.
307 sceneDesc.flags |= physx::PxSceneFlag::eENABLE_CCD;
308 sceneDesc.solverType = physx::PxSolverType::eTGS;
309 sceneDesc.simulationEventCallback = callback;
310
311 if (physicsWorld->reportKinematicKinematicCollisions())
312 sceneDesc.kineKineFilteringMode = physx::PxPairFilteringMode::eKEEP;
313 if (physicsWorld->reportStaticKinematicCollisions())
314 sceneDesc.staticKineFilteringMode = physx::PxPairFilteringMode::eKEEP;
315
316 switch (physicsWorld->staticQueryStructure()) {
317 case QPhysicsWorld::QueryStructure::NoStructure: {
318 Q_ASSERT(false && "Unreachable: setStaticQueryStructure() rejects NoStructure");
319 break;
320 }
321
322 case QPhysicsWorld::QueryStructure::StaticTree: {
323 sceneDesc.staticStructure = physx::PxPruningStructureType::eSTATIC_AABB_TREE;
324 break;
325 }
326
327 case QPhysicsWorld::QueryStructure::DynamicTree: {
328 sceneDesc.staticStructure = physx::PxPruningStructureType::eDYNAMIC_AABB_TREE;
329 break;
330 }
331
332 }
333
334 switch (physicsWorld->dynamicQueryStructure()) {
335 case QPhysicsWorld::QueryStructure::NoStructure: {
336 sceneDesc.dynamicStructure = physx::PxPruningStructureType::eNONE;
337 break;
338 }
339
340 case QPhysicsWorld::QueryStructure::StaticTree: {
341 sceneDesc.dynamicStructure = physx::PxPruningStructureType::eSTATIC_AABB_TREE;
342 break;
343 }
344
345 case QPhysicsWorld::QueryStructure::DynamicTree: {
346 sceneDesc.dynamicStructure = physx::PxPruningStructureType::eDYNAMIC_AABB_TREE;
347 break;
348 }
349
350 }
351
352 scene = s_physx.physics->createScene(sceneDesc);
353}
354
void createScene(float typicalLength, float typicalSpeed, const QVector3D &gravity, QPhysicsWorld *physicsWorld, unsigned int numThreads)
void createWorld()
SimulationEventCallback * callback
void deleteWorld()
physx::PxScene * scene
physx::PxControllerManager * controllerManager
The QVector3D class represents a vector or vertex in 3D space.
Definition qvectornd.h:178
SimulationEventCallback(QPhysicsWorld *worldIn)
void onConstraintBreak(physx::PxConstraintInfo *, physx::PxU32) override
virtual ~SimulationEventCallback()=default
void onContact(const physx::PxContactPairHeader &pairHeader, const physx::PxContactPair *pairs, physx::PxU32 nbPairs) override
void onAdvance(const physx::PxRigidBody *const *, const physx::PxTransform *, const physx::PxU32) override
void onWake(physx::PxActor **, physx::PxU32) override
void onTrigger(physx::PxTriggerPair *pairs, physx::PxU32 count) override
void onSleep(physx::PxActor **, physx::PxU32) override
#define PHYSX_RELEASE(x)
static constexpr bool isBitSet(quint32 value, quint32 position)
static bool isFilteredOut(physx::PxFilterData filterData0, physx::PxFilterData filterData1)
static physx::PxFilterFlags contactReportFilterShader(physx::PxFilterObjectAttributes attributes0, physx::PxFilterData filterData0, physx::PxFilterObjectAttributes attributes1, physx::PxFilterData filterData1, physx::PxPairFlags &pairFlags, const void *, physx::PxU32)
#define QT_BEGIN_NAMESPACE
#define QT_END_NAMESPACE
physx::PxCooking * cooking
static StaticPhysXObjects & getReference()
physx::PxFoundation * foundation
physx::PxPvdTransport * transport
physx::PxDefaultCpuDispatcher * dispatcher
physx::PxPhysics * physics