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 "extensions/PxDefaultCpuDispatcher.h"
9#include "foundation/PxFoundation.h"
10#include "PxPhysics.h"
11#include "foundation/PxPhysicsVersion.h"
12#include "PxRigidActor.h"
13#include "PxScene.h"
14#include "PxShape.h"
15#include "PxSimulationEventCallback.h"
16
22#include "qtriggerbody_p.h"
23
25
26// The shape a PhysX shape was built for, or null if it was not built for one.
27// Only ever compared, since the shape can be gone by the time it is looked at.
28static QAbstractCollisionShape *frontendShape(const physx::PxShape *shape)
29{
30 return shape ? static_cast<QAbstractCollisionShape *>(shape->userData) : nullptr;
31}
32
34{
35public:
36 SimulationEventCallback(QPhysicsWorld *worldIn) : world(worldIn) {};
37 virtual ~SimulationEventCallback() = default;
38
39 void onTrigger(physx::PxTriggerPair *pairs, physx::PxU32 count) override
40 {
41 // A body one of whose shapes leaves a trigger as another of them enters
42 // it has a lost and a found pair in the same batch, and taking the lost
43 // one first would report the body as having left and entered again. So
44 // the found pair is taken first, and only for the trigger and body that
45 // has both: every other pair is handled in the order PhysX gave it, so
46 // a body leaving one trigger as it enters another is still reported that
47 // way.
48 const auto replacesLostOverlap = [pairs, count](physx::PxU32 i) {
49 if (pairs[i].status != physx::PxPairFlag::eNOTIFY_TOUCH_FOUND)
50 return false;
51 for (physx::PxU32 j = 0; j < count; j++) {
52 if (pairs[j].status == physx::PxPairFlag::eNOTIFY_TOUCH_LOST
53 && pairs[j].triggerActor == pairs[i].triggerActor
54 && pairs[j].otherActor == pairs[i].otherActor) {
55 return true;
56 }
57 }
58 return false;
59 };
60
61 // Those first, then every pair in turn. Registering one of them again
62 // on the second pass does nothing: the body is inside already and the
63 // pair is one of its own already.
64 enum Pass { ReplacedOverlaps, EveryPair };
65 for (const Pass pass : { ReplacedOverlaps, EveryPair }) {
66 for (physx::PxU32 i = 0; i < count; i++) {
67 if (pass == ReplacedOverlaps && !replacesLostOverlap(i))
68 continue;
69
70 // ignore pairs when shapes have been deleted
71 if (pairs[i].flags
72 & (physx::PxTriggerPairFlag::eREMOVED_SHAPE_TRIGGER
73 | physx::PxTriggerPairFlag::eREMOVED_SHAPE_OTHER))
74 continue;
75
76 QTriggerBody *triggerNode =
77 static_cast<QTriggerBody *>(pairs[i].triggerActor->userData);
78
79 QAbstractPhysicsNode *otherNode =
80 static_cast<QAbstractPhysicsNode *>(pairs[i].otherActor->userData);
81
82 if (!triggerNode || !otherNode) {
83 qWarning() << "QtQuick3DPhysics internal error: null pointer in trigger "
84 "collision.";
85 continue;
86 }
87
88 // Asked per pair, since one batch can report the same node in
89 // several pairs and a handler run for one of them can delete a
90 // node a later one points at. Nothing else can: PhysX sends
91 // these from the thread that deletes nodes.
92 if (world->isNodeRemoved(triggerNode) || world->isNodeRemoved(otherNode))
93 continue;
94
95 // Passed on so the trigger can tell one pair of a body from
96 // another: PhysX reports a pair of shapes and a body is several
97 // of those, so one of them stopping overlapping is not the body
98 // leaving.
99 const QTriggerBody::ShapePair shapes { frontendShape(pairs[i].triggerShape),
100 frontendShape(pairs[i].otherShape) };
101
102 // Whether a body is worth following, and what it is owed, is
103 // the trigger's to decide, once per body rather than once per
104 // pair: a body that changes its mind while inside would
105 // otherwise be half followed.
106 if (pairs[i].status == physx::PxPairFlag::eNOTIFY_TOUCH_FOUND)
107 triggerNode->registerCollision(otherNode, shapes);
108 else if (pairs[i].status == physx::PxPairFlag::eNOTIFY_TOUCH_LOST)
109 triggerNode->deregisterCollision(otherNode, shapes);
110 }
111 }
112 }
113
114 void onConstraintBreak(physx::PxConstraintInfo * /*constraints*/,
115 physx::PxU32 /*count*/) override {};
116 void onWake(physx::PxActor ** /*actors*/, physx::PxU32 /*count*/) override {};
117 void onSleep(physx::PxActor ** /*actors*/, physx::PxU32 /*count*/) override {};
118 void onContact(const physx::PxContactPairHeader &pairHeader, const physx::PxContactPair *pairs,
119 physx::PxU32 nbPairs) override
120 {
121 constexpr physx::PxU32 bufferSize = 64;
122 physx::PxContactPairPoint contacts[bufferSize];
123
124 for (physx::PxU32 i = 0; i < nbPairs; i++) {
125 const physx::PxContactPair &contactPair = pairs[i];
126
127 if (contactPair.events & physx::PxPairFlag::eNOTIFY_TOUCH_FOUND) {
128 QAbstractPhysicsNode *trigger =
129 static_cast<QAbstractPhysicsNode *>(pairHeader.actors[0]->userData);
130 QAbstractPhysicsNode *other =
131 static_cast<QAbstractPhysicsNode *>(pairHeader.actors[1]->userData);
132
133 if (!trigger || !other || world->isNodeRemoved(trigger)
134 || world->isNodeRemoved(other) || !trigger->m_backendObject
135 || !other->m_backendObject)
136 continue;
137
138 const bool triggerReceive =
139 trigger->receiveContactReports() && other->sendContactReports();
140 const bool otherReceive =
141 other->receiveContactReports() && trigger->sendContactReports();
142
143 if (!triggerReceive && !otherReceive)
144 continue;
145
146 physx::PxU32 nbContacts = pairs[i].extractContacts(contacts, bufferSize);
147
148 QList<QVector3D> positions;
149 QList<QVector3D> impulses;
150 QList<QVector3D> normals;
151
152 positions.reserve(nbContacts);
153 impulses.reserve(nbContacts);
154 normals.reserve(nbContacts);
155
156 for (physx::PxU32 j = 0; j < nbContacts; j++) {
157 physx::PxVec3 position = contacts[j].position;
158 physx::PxVec3 impulse = contacts[j].impulse;
159 physx::PxVec3 normal = contacts[j].normal;
160
161 positions.push_back(QPhysicsUtils::toQtType(position));
162 impulses.push_back(QPhysicsUtils::toQtType(impulse));
163 normals.push_back(QPhysicsUtils::toQtType(normal));
164 }
165
166 QList<QVector3D> normalsInverted;
167 normalsInverted.reserve(normals.size());
168 for (const QVector3D &v : std::as_const(normals)) {
169 normalsInverted.push_back(QVector3D(-v.x(), -v.y(), -v.z()));
170 }
171
172 if (triggerReceive)
173 world->registerContact(other, trigger, positions, impulses, normals);
174 if (otherReceive)
175 world->registerContact(trigger, other, positions, impulses, normalsInverted);
176 }
177 }
178 };
179 void onAdvance(const physx::PxRigidBody *const * /*bodyBuffer*/,
180 const physx::PxTransform * /*poseBuffer*/,
181 const physx::PxU32 /*count*/) override {};
182
183private:
184 QPhysicsWorld *world = nullptr;
185};
186
187static constexpr bool isBitSet(quint32 value, quint32 position)
188{
189 Q_ASSERT(position <= 32);
190 return value & (1 << (position));
191}
192
193// If any 'id' bit is set in the other mask it means collisions should be ignored, i.e.
194static bool isFilteredOut(physx::PxFilterData filterData0, physx::PxFilterData filterData1)
195{
196 // First word is id, second is collision mask
197 const quint32 id0 = filterData0.word0;
198 const quint32 id1 = filterData1.word0;
199 const quint32 mask0 = filterData0.word1;
200 const quint32 mask1 = filterData1.word1;
201
202 return id0 < 32 && id1 < 32 && (isBitSet(mask0, id1) || isBitSet(mask1, id0));
203}
204
205static physx::PxFilterFlags
206contactReportFilterShader(physx::PxFilterObjectAttributes attributes0,
207 physx::PxFilterData filterData0,
208 physx::PxFilterObjectAttributes attributes1,
209 physx::PxFilterData filterData1, physx::PxPairFlags &pairFlags,
210 const void * /*constantBlock*/, physx::PxU32 /*constantBlockSize*/)
211{
212 if (isFilteredOut(filterData0, filterData1)) {
213 // We return a 'suppress' since that will still re-evaluate when filter data is changed.
214 return physx::PxFilterFlag::eSUPPRESS;
215 }
216
217 // A trigger shape does not collide, it only reports what overlaps it, so
218 // there is nothing to solve and nothing for a continuous sweep to do. The
219 // scene has CCD enabled unconditionally (see below), and PhysX warns for
220 // every trigger pair that asks for a CCD contact, so ask only for what a
221 // trigger actually needs.
222 if (physx::PxFilterObjectIsTrigger(attributes0)
223 || physx::PxFilterObjectIsTrigger(attributes1)) {
224 pairFlags = physx::PxPairFlag::eTRIGGER_DEFAULT;
225 return physx::PxFilterFlag::eDEFAULT;
226 }
227
228 // Makes objects collide
229 const auto defaultCollisonFlags =
230 physx::PxPairFlag::eSOLVE_CONTACT |
231 physx::PxPairFlag::eDETECT_DISCRETE_CONTACT |
232 physx::PxPairFlag::eDETECT_CCD_CONTACT; // will be ignored by physX engine if the ccd is disabled
233
234 // For trigger body detection
235 const auto notifyTouchFlags =
236 physx::PxPairFlag::eNOTIFY_TOUCH_FOUND | physx::PxPairFlag::eNOTIFY_TOUCH_LOST;
237
238 // For contact detection
239 const auto notifyContactFlags = physx::PxPairFlag::eNOTIFY_CONTACT_POINTS;
240
241 pairFlags = defaultCollisonFlags | notifyTouchFlags | notifyContactFlags;
242 return physx::PxFilterFlag::eDEFAULT;
243}
244
245#define PHYSX_RELEASE(x)
246 if (x != nullptr) {
247 x->release();
248 x = nullptr;
249 }
250
252{
254 s_physx.foundationRefCount++;
255
256 if (s_physx.foundationCreated)
257 return;
258
259 s_physx.foundation = PxCreateFoundation(
260 PX_PHYSICS_VERSION, s_physx.defaultAllocatorCallback, s_physx.defaultErrorCallback);
261 if (!s_physx.foundation)
262 qFatal("PxCreateFoundation failed!");
263
264 s_physx.foundationCreated = true;
265}
266
268{
270 s_physx.foundationRefCount--;
271 if (s_physx.foundationRefCount == 0) {
275 // Every node releases its material when it is cleaned up, which happens before the
276 // last world is deleted, so nothing should be left to release here
277 Q_ASSERT(s_physx.materials.isEmpty());
278 s_physx.materials.clear();
279 PHYSX_RELEASE(s_physx.physics);
281
282 delete callback;
283 callback = nullptr;
284 s_physx.foundationCreated = false;
285 s_physx.physicsCreated = false;
286 } else {
287 delete callback;
288 callback = nullptr;
291 }
292}
293
294void QPhysXWorld::createScene(float typicalLength, float typicalSpeed, const QVector3D &gravity,
295 QPhysicsWorld *physicsWorld, unsigned int numThreads)
296{
297 if (scene) {
298 qWarning() << "Scene already created";
299 return;
300 }
301
302 physx::PxTolerancesScale scale;
303 scale.length = typicalLength;
304 scale.speed = typicalSpeed;
305
307
308 if (!s_physx.physicsCreated) {
309 constexpr bool recordMemoryAllocations = true;
310 s_physx.physics = PxCreatePhysics(PX_PHYSICS_VERSION, *s_physx.foundation, scale,
311 recordMemoryAllocations);
312 if (!s_physx.physics)
313 qFatal("PxCreatePhysics failed!");
314
315 s_physx.dispatcher = physx::PxDefaultCpuDispatcherCreate(numThreads);
316 s_physx.physicsCreated = true;
317 }
318
319 callback = new SimulationEventCallback(physicsWorld);
320
321 physx::PxSceneDesc sceneDesc(scale);
322 sceneDesc.gravity = QPhysicsUtils::toPhysXType(gravity);
323 sceneDesc.cpuDispatcher = s_physx.dispatcher;
324
325 sceneDesc.filterShader = contactReportFilterShader;
326 // CCD is always enabled at the scene level. Since PhysX's CCD pass is cheap enough
327 // (see PxsCCDContext::updateCCD) when there are no CCD-enabled bodies in the scene,
328 // it makes the code simpler.
329 sceneDesc.flags |= physx::PxSceneFlag::eENABLE_CCD;
330 sceneDesc.solverType = physx::PxSolverType::eTGS;
331 sceneDesc.simulationEventCallback = callback;
332
333 if (physicsWorld->reportKinematicKinematicCollisions())
334 sceneDesc.kineKineFilteringMode = physx::PxPairFilteringMode::eKEEP;
335 if (physicsWorld->reportStaticKinematicCollisions())
336 sceneDesc.staticKineFilteringMode = physx::PxPairFilteringMode::eKEEP;
337
338 switch (physicsWorld->staticQueryStructure()) {
339 case QPhysicsWorld::QueryStructure::NoStructure: {
340 Q_ASSERT(false && "Unreachable: setStaticQueryStructure() rejects NoStructure");
341 break;
342 }
343
344 case QPhysicsWorld::QueryStructure::StaticTree: {
345 sceneDesc.staticStructure = physx::PxPruningStructureType::eSTATIC_AABB_TREE;
346 break;
347 }
348
349 case QPhysicsWorld::QueryStructure::DynamicTree: {
350 sceneDesc.staticStructure = physx::PxPruningStructureType::eDYNAMIC_AABB_TREE;
351 break;
352 }
353
354 }
355
356 switch (physicsWorld->dynamicQueryStructure()) {
357 case QPhysicsWorld::QueryStructure::NoStructure: {
358 sceneDesc.dynamicStructure = physx::PxPruningStructureType::eNONE;
359 break;
360 }
361
362 case QPhysicsWorld::QueryStructure::StaticTree: {
363 sceneDesc.dynamicStructure = physx::PxPruningStructureType::eSTATIC_AABB_TREE;
364 break;
365 }
366
367 case QPhysicsWorld::QueryStructure::DynamicTree: {
368 sceneDesc.dynamicStructure = physx::PxPruningStructureType::eDYNAMIC_AABB_TREE;
369 break;
370 }
371
372 }
373
374 scene = s_physx.physics->createScene(sceneDesc);
375}
376
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
PxTransformT< float > PxTransform
#define PHYSX_RELEASE(x)
static QT_BEGIN_NAMESPACE QAbstractCollisionShape * frontendShape(const physx::PxShape *shape)
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
static StaticPhysXObjects & getReference()
physx::PxFoundation * foundation
physx::PxDefaultCpuDispatcher * dispatcher
physx::PxPhysics * physics