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 if (pairs->status == physx::PxPairFlag::eNOTIFY_TOUCH_FOUND) {
58 if (otherNode->sendTriggerReports()) {
59 triggerNode->registerCollision(otherNode);
60 }
61 if (otherNode->receiveTriggerReports()) {
62 emit otherNode->enteredTriggerBody(triggerNode);
63 }
64 } else if (pairs->status == physx::PxPairFlag::eNOTIFY_TOUCH_LOST) {
65 if (otherNode->sendTriggerReports()) {
66 triggerNode->deregisterCollision(otherNode);
67 }
68 if (otherNode->receiveTriggerReports()) {
69 emit otherNode->exitedTriggerBody(triggerNode);
70 }
71 }
72 }
73 }
74
75 void onConstraintBreak(physx::PxConstraintInfo * /*constraints*/,
76 physx::PxU32 /*count*/) override {};
77 void onWake(physx::PxActor ** /*actors*/, physx::PxU32 /*count*/) override {};
78 void onSleep(physx::PxActor ** /*actors*/, physx::PxU32 /*count*/) override {};
79 void onContact(const physx::PxContactPairHeader &pairHeader, const physx::PxContactPair *pairs,
80 physx::PxU32 nbPairs) override
81 {
82 QMutexLocker locker(&world->m_removedPhysicsNodesMutex);
83 constexpr physx::PxU32 bufferSize = 64;
84 physx::PxContactPairPoint contacts[bufferSize];
85
86 for (physx::PxU32 i = 0; i < nbPairs; i++) {
87 const physx::PxContactPair &contactPair = pairs[i];
88
89 if (contactPair.events & physx::PxPairFlag::eNOTIFY_TOUCH_FOUND) {
90 QAbstractPhysicsNode *trigger =
91 static_cast<QAbstractPhysicsNode *>(pairHeader.actors[0]->userData);
92 QAbstractPhysicsNode *other =
93 static_cast<QAbstractPhysicsNode *>(pairHeader.actors[1]->userData);
94
95 if (!trigger || !other || world->isNodeRemoved(trigger)
96 || world->isNodeRemoved(other) || !trigger->m_backendObject
97 || !other->m_backendObject)
98 continue;
99
100 const bool triggerReceive =
101 trigger->receiveContactReports() && other->sendContactReports();
102 const bool otherReceive =
103 other->receiveContactReports() && trigger->sendContactReports();
104
105 if (!triggerReceive && !otherReceive)
106 continue;
107
108 physx::PxU32 nbContacts = pairs[i].extractContacts(contacts, bufferSize);
109
110 QList<QVector3D> positions;
111 QList<QVector3D> impulses;
112 QList<QVector3D> normals;
113
114 positions.reserve(nbContacts);
115 impulses.reserve(nbContacts);
116 normals.reserve(nbContacts);
117
118 for (physx::PxU32 j = 0; j < nbContacts; j++) {
119 physx::PxVec3 position = contacts[j].position;
120 physx::PxVec3 impulse = contacts[j].impulse;
121 physx::PxVec3 normal = contacts[j].normal;
122
123 positions.push_back(QPhysicsUtils::toQtType(position));
124 impulses.push_back(QPhysicsUtils::toQtType(impulse));
125 normals.push_back(QPhysicsUtils::toQtType(normal));
126 }
127
128 QList<QVector3D> normalsInverted;
129 normalsInverted.reserve(normals.size());
130 for (const QVector3D &v : normals) {
131 normalsInverted.push_back(QVector3D(-v.x(), -v.y(), -v.z()));
132 }
133
134 if (triggerReceive)
135 world->registerContact(other, trigger, positions, impulses, normals);
136 if (otherReceive)
137 world->registerContact(trigger, other, positions, impulses, normalsInverted);
138 }
139 }
140 };
141 void onAdvance(const physx::PxRigidBody *const * /*bodyBuffer*/,
142 const physx::PxTransform * /*poseBuffer*/,
143 const physx::PxU32 /*count*/) override {};
144
145private:
146 QPhysicsWorld *world = nullptr;
147};
148
149static constexpr bool isBitSet(quint32 value, quint32 position)
150{
151 Q_ASSERT(position <= 32);
152 return value & (1 << (position));
153}
154
155static physx::PxFilterFlags
157 physx::PxFilterData filterData0,
158 physx::PxFilterObjectAttributes /*attributes1*/,
159 physx::PxFilterData filterData1, physx::PxPairFlags &pairFlags,
160 const void * /*constantBlock*/, physx::PxU32 /*constantBlockSize*/)
161{
162 // First word is id, second is collision mask
163 const quint32 id0 = filterData0.word0;
164 const quint32 id1 = filterData1.word0;
165 const quint32 mask0 = filterData0.word1;
166 const quint32 mask1 = filterData1.word1;
167
168 // If any 'id' bit is set in the other mask it means collisions should be ignored
169 if (id0 < 32 && id1 < 32 && (isBitSet(mask0, id1) || isBitSet(mask1, id0))) {
170 // We return a 'suppress' since that will still re-evaluate when filter data is changed.
171 return physx::PxFilterFlag::eSUPPRESS;
172 }
173
174 // Makes objects collide
175 const auto defaultCollisonFlags =
176 physx::PxPairFlag::eSOLVE_CONTACT | physx::PxPairFlag::eDETECT_DISCRETE_CONTACT;
177
178 // For trigger body detection
179 const auto notifyTouchFlags =
180 physx::PxPairFlag::eNOTIFY_TOUCH_FOUND | physx::PxPairFlag::eNOTIFY_TOUCH_LOST;
181
182 // For contact detection
183 const auto notifyContactFlags = physx::PxPairFlag::eNOTIFY_CONTACT_POINTS;
184
185 pairFlags = defaultCollisonFlags | notifyTouchFlags | notifyContactFlags;
186 return physx::PxFilterFlag::eDEFAULT;
187}
188
189static physx::PxFilterFlags
191 physx::PxFilterData /*filterData0*/,
192 physx::PxFilterObjectAttributes /*attributes1*/,
193 physx::PxFilterData /*filterData1*/, physx::PxPairFlags &pairFlags,
194 const void * /*constantBlock*/, physx::PxU32 /*constantBlockSize*/)
195{
196 // Makes objects collide
197 const auto defaultCollisonFlags = physx::PxPairFlag::eSOLVE_CONTACT
198 | physx::PxPairFlag::eDETECT_DISCRETE_CONTACT | physx::PxPairFlag::eDETECT_CCD_CONTACT;
199
200 // For trigger body detection
201 const auto notifyTouchFlags =
202 physx::PxPairFlag::eNOTIFY_TOUCH_FOUND | physx::PxPairFlag::eNOTIFY_TOUCH_LOST;
203
204 // For contact detection
205 const auto notifyContactFlags = physx::PxPairFlag::eNOTIFY_CONTACT_POINTS;
206
207 pairFlags = defaultCollisonFlags | notifyTouchFlags | notifyContactFlags;
208 return physx::PxFilterFlag::eDEFAULT;
209}
210
211#define PHYSX_RELEASE(x)
212 if (x != nullptr) {
213 x->release();
214 x = nullptr;
215 }
216
218{
220 s_physx.foundationRefCount++;
221
222 if (s_physx.foundationCreated)
223 return;
224
225 s_physx.foundation = PxCreateFoundation(
226 PX_PHYSICS_VERSION, s_physx.defaultAllocatorCallback, s_physx.defaultErrorCallback);
227 if (!s_physx.foundation)
228 qFatal("PxCreateFoundation failed!");
229
230 s_physx.foundationCreated = true;
231
232#if PHYSX_ENABLE_PVD
233 s_physx.pvd = PxCreatePvd(*m_physx->foundation);
234 s_physx.transport = physx::PxDefaultPvdSocketTransportCreate("qt", 5425, 10);
235 s_physx.pvd->connect(*m_physx->transport, physx::PxPvdInstrumentationFlag::eALL);
236#endif
237
238 // FIXME: does the tolerance matter?
239 s_physx.cooking = PxCreateCooking(PX_PHYSICS_VERSION, *s_physx.foundation,
240 physx::PxCookingParams(physx::PxTolerancesScale()));
241
242}
243
245{
247 s_physx.foundationRefCount--;
248 if (s_physx.foundationRefCount == 0) {
252 PHYSX_RELEASE(s_physx.cooking);
254 PHYSX_RELEASE(s_physx.pvd);
255 PHYSX_RELEASE(s_physx.physics);
257
258 delete callback;
259 callback = nullptr;
260 s_physx.foundationCreated = false;
261 s_physx.physicsCreated = false;
262 } else {
263 delete callback;
264 callback = nullptr;
267 }
268}
269
270void QPhysXWorld::createScene(float typicalLength, float typicalSpeed, const QVector3D &gravity,
271 bool enableCCD, QPhysicsWorld *physicsWorld, unsigned int numThreads)
272{
273 if (scene) {
274 qWarning() << "Scene already created";
275 return;
276 }
277
278 physx::PxTolerancesScale scale;
279 scale.length = typicalLength;
280 scale.speed = typicalSpeed;
281
283
284 if (!s_physx.physicsCreated) {
285 constexpr bool recordMemoryAllocations = true;
286 s_physx.physics = PxCreatePhysics(PX_PHYSICS_VERSION, *s_physx.foundation, scale,
287 recordMemoryAllocations, s_physx.pvd);
288 if (!s_physx.physics)
289 qFatal("PxCreatePhysics failed!");
290
291 s_physx.dispatcher = physx::PxDefaultCpuDispatcherCreate(numThreads);
292 s_physx.physicsCreated = true;
293 }
294
295 callback = new SimulationEventCallback(physicsWorld);
296
297 physx::PxSceneDesc sceneDesc(scale);
298 sceneDesc.gravity = QPhysicsUtils::toPhysXType(gravity);
299 sceneDesc.cpuDispatcher = s_physx.dispatcher;
300
301 if (enableCCD) {
302 sceneDesc.filterShader = contactReportFilterShaderCCD;
303 sceneDesc.flags |= physx::PxSceneFlag::eENABLE_CCD;
304 } else {
305 sceneDesc.filterShader = contactReportFilterShader;
306 }
307 sceneDesc.solverType = physx::PxSolverType::eTGS;
308 sceneDesc.simulationEventCallback = callback;
309
310 if (physicsWorld->reportKinematicKinematicCollisions())
311 sceneDesc.kineKineFilteringMode = physx::PxPairFilteringMode::eKEEP;
312 if (physicsWorld->reportStaticKinematicCollisions())
313 sceneDesc.staticKineFilteringMode = physx::PxPairFilteringMode::eKEEP;
314
315 scene = s_physx.physics->createScene(sceneDesc);
316}
317
void createWorld()
SimulationEventCallback * callback
void deleteWorld()
physx::PxScene * scene
void createScene(float typicalLength, float typicalSpeed, const QVector3D &gravity, bool enableCCD, QPhysicsWorld *physicsWorld, unsigned int numThreads)
physx::PxControllerManager * controllerManager
The QVector3D class represents a vector or vertex in 3D space.
Definition qvectornd.h:172
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 physx::PxFilterFlags contactReportFilterShaderCCD(physx::PxFilterObjectAttributes, physx::PxFilterData, physx::PxFilterObjectAttributes, physx::PxFilterData, physx::PxPairFlags &pairFlags, const void *, physx::PxU32)
static constexpr bool isBitSet(quint32 value, quint32 position)
static physx::PxFilterFlags contactReportFilterShader(physx::PxFilterObjectAttributes, physx::PxFilterData filterData0, physx::PxFilterObjectAttributes, 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