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
23QT_BEGIN_NAMESPACE
24
25class SimulationEventCallback : public physx::PxSimulationEventCallback
26{
27public:
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 onContact(const physx::PxContactPairHeader &pairHeader, 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 || !trigger->m_backendObject || !other->m_backendObject
95 || world->isNodeRemoved(object: trigger) || world->isNodeRemoved(object: other))
96 continue;
97
98 const bool triggerReceive =
99 trigger->receiveContactReports() && other->sendContactReports();
100 const bool otherReceive =
101 other->receiveContactReports() && trigger->sendContactReports();
102
103 if (!triggerReceive && !otherReceive)
104 continue;
105
106 physx::PxU32 nbContacts = pairs[i].extractContacts(userBuffer: contacts, bufferSize);
107
108 QList<QVector3D> positions;
109 QList<QVector3D> impulses;
110 QList<QVector3D> normals;
111
112 positions.reserve(asize: nbContacts);
113 impulses.reserve(asize: nbContacts);
114 normals.reserve(asize: nbContacts);
115
116 for (physx::PxU32 j = 0; j < nbContacts; j++) {
117 physx::PxVec3 position = contacts[j].position;
118 physx::PxVec3 impulse = contacts[j].impulse;
119 physx::PxVec3 normal = contacts[j].normal;
120
121 positions.push_back(t: QPhysicsUtils::toQtType(vec: position));
122 impulses.push_back(t: QPhysicsUtils::toQtType(vec: impulse));
123 normals.push_back(t: QPhysicsUtils::toQtType(vec: normal));
124 }
125
126 QList<QVector3D> normalsInverted;
127 normalsInverted.reserve(asize: normals.size());
128 for (const QVector3D &v : normals) {
129 normalsInverted.push_back(t: QVector3D(-v.x(), -v.y(), -v.z()));
130 }
131
132 if (triggerReceive)
133 trigger->registerContact(body: other, positions, impulses, normals);
134 if (otherReceive)
135 other->registerContact(body: trigger, positions, impulses, normals: normalsInverted);
136 }
137 }
138 };
139 void onAdvance(const physx::PxRigidBody *const * /*bodyBuffer*/,
140 const physx::PxTransform * /*poseBuffer*/,
141 const physx::PxU32 /*count*/) override {};
142
143private:
144 QPhysicsWorld *world = nullptr;
145};
146
147static physx::PxFilterFlags
148contactReportFilterShader(physx::PxFilterObjectAttributes /*attributes0*/,
149 physx::PxFilterData /*filterData0*/,
150 physx::PxFilterObjectAttributes /*attributes1*/,
151 physx::PxFilterData /*filterData1*/, physx::PxPairFlags &pairFlags,
152 const void * /*constantBlock*/, physx::PxU32 /*constantBlockSize*/)
153{
154 // Makes objects collide
155 const auto defaultCollisonFlags =
156 physx::PxPairFlag::eSOLVE_CONTACT | physx::PxPairFlag::eDETECT_DISCRETE_CONTACT;
157
158 // For trigger body detection
159 const auto notifyTouchFlags =
160 physx::PxPairFlag::eNOTIFY_TOUCH_FOUND | physx::PxPairFlag::eNOTIFY_TOUCH_LOST;
161
162 // For contact detection
163 const auto notifyContactFlags = physx::PxPairFlag::eNOTIFY_CONTACT_POINTS;
164
165 pairFlags = defaultCollisonFlags | notifyTouchFlags | notifyContactFlags;
166 return physx::PxFilterFlag::eDEFAULT;
167}
168
169static physx::PxFilterFlags
170contactReportFilterShaderCCD(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 // Makes objects collide
177 const auto defaultCollisonFlags = physx::PxPairFlag::eSOLVE_CONTACT
178 | physx::PxPairFlag::eDETECT_DISCRETE_CONTACT | physx::PxPairFlag::eDETECT_CCD_CONTACT;
179
180 // For trigger body detection
181 const auto notifyTouchFlags =
182 physx::PxPairFlag::eNOTIFY_TOUCH_FOUND | physx::PxPairFlag::eNOTIFY_TOUCH_LOST;
183
184 // For contact detection
185 const auto notifyContactFlags = physx::PxPairFlag::eNOTIFY_CONTACT_POINTS;
186
187 pairFlags = defaultCollisonFlags | notifyTouchFlags | notifyContactFlags;
188 return physx::PxFilterFlag::eDEFAULT;
189}
190
191#define PHYSX_RELEASE(x) \
192 if (x != nullptr) { \
193 x->release(); \
194 x = nullptr; \
195 }
196
197void QPhysXWorld::createWorld()
198{
199 auto &s_physx = StaticPhysXObjects::getReference();
200 s_physx.foundationRefCount++;
201
202 if (s_physx.foundationCreated)
203 return;
204
205 s_physx.foundation = PxCreateFoundation(
206 PX_PHYSICS_VERSION, allocator&: s_physx.defaultAllocatorCallback, errorCallback&: s_physx.defaultErrorCallback);
207 if (!s_physx.foundation)
208 qFatal(msg: "PxCreateFoundation failed!");
209
210 s_physx.foundationCreated = true;
211
212#if PHYSX_ENABLE_PVD
213 s_physx.pvd = PxCreatePvd(*m_physx->foundation);
214 s_physx.transport = physx::PxDefaultPvdSocketTransportCreate("qt", 5425, 10);
215 s_physx.pvd->connect(*m_physx->transport, physx::PxPvdInstrumentationFlag::eALL);
216#endif
217
218 // FIXME: does the tolerance matter?
219 s_physx.cooking = PxCreateCooking(PX_PHYSICS_VERSION, foundation&: *s_physx.foundation,
220 params: physx::PxCookingParams(physx::PxTolerancesScale()));
221
222}
223
224void QPhysXWorld::deleteWorld()
225{
226 auto &s_physx = StaticPhysXObjects::getReference();
227 s_physx.foundationRefCount--;
228 if (s_physx.foundationRefCount == 0) {
229 PHYSX_RELEASE(controllerManager);
230 PHYSX_RELEASE(scene);
231 PHYSX_RELEASE(s_physx.dispatcher);
232 PHYSX_RELEASE(s_physx.cooking);
233 PHYSX_RELEASE(s_physx.transport);
234 PHYSX_RELEASE(s_physx.pvd);
235 PHYSX_RELEASE(s_physx.physics);
236 PHYSX_RELEASE(s_physx.foundation);
237
238 delete callback;
239 callback = nullptr;
240 s_physx.foundationCreated = false;
241 s_physx.physicsCreated = false;
242 } else {
243 delete callback;
244 callback = nullptr;
245 PHYSX_RELEASE(controllerManager);
246 PHYSX_RELEASE(scene);
247 }
248}
249
250void QPhysXWorld::createScene(float typicalLength, float typicalSpeed, const QVector3D &gravity,
251 bool enableCCD, QPhysicsWorld *physicsWorld)
252{
253 if (scene) {
254 qWarning() << "Scene already created";
255 return;
256 }
257
258 physx::PxTolerancesScale scale;
259 scale.length = typicalLength;
260 scale.speed = typicalSpeed;
261
262 auto &s_physx = StaticPhysXObjects::getReference();
263
264 if (!s_physx.physicsCreated) {
265 constexpr bool recordMemoryAllocations = true;
266 s_physx.physics = PxCreatePhysics(PX_PHYSICS_VERSION, foundation&: *s_physx.foundation, scale,
267 trackOutstandingAllocations: recordMemoryAllocations, pvd: s_physx.pvd);
268 if (!s_physx.physics)
269 qFatal(msg: "PxCreatePhysics failed!");
270 s_physx.dispatcher = physx::PxDefaultCpuDispatcherCreate(numThreads: 2);
271 s_physx.physicsCreated = true;
272 }
273
274 callback = new SimulationEventCallback(physicsWorld);
275
276 physx::PxSceneDesc sceneDesc(scale);
277 sceneDesc.gravity = QPhysicsUtils::toPhysXType(qvec: gravity);
278 sceneDesc.cpuDispatcher = s_physx.dispatcher;
279
280 if (enableCCD) {
281 sceneDesc.filterShader = contactReportFilterShaderCCD;
282 sceneDesc.flags |= physx::PxSceneFlag::eENABLE_CCD;
283 } else {
284 sceneDesc.filterShader = contactReportFilterShader;
285 }
286 sceneDesc.solverType = physx::PxSolverType::eTGS;
287 sceneDesc.simulationEventCallback = callback;
288
289 scene = s_physx.physics->createScene(sceneDesc);
290}
291
292QT_END_NAMESPACE
293

source code of qtquick3dphysics/src/quick3dphysics/physxnode/qphysxworld.cpp