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
qphysxcharactercontroller.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 "PxRigidDynamic.h"
8#include "PxShape.h"
9#include "characterkinematic/PxController.h"
10#include "characterkinematic/PxControllerManager.h"
11#include "characterkinematic/PxCapsuleController.h"
12
17
18#define PHYSX_RELEASE(x)
19 if (x != nullptr) {
20 x->release();
21 x = nullptr;
22 }
23
25
26class ControllerCallback : public physx::PxUserControllerHitReport
27{
28public:
29 ControllerCallback(QPhysicsWorld *worldIn) : world(worldIn) { }
30
31 void onShapeHit(const physx::PxControllerShapeHit &hit) override
32 {
33 QAbstractPhysicsNode *other = static_cast<QAbstractPhysicsNode *>(hit.actor->userData);
34 QCharacterController *trigger =
35 static_cast<QCharacterController *>(hit.controller->getUserData());
36
37 if (!trigger || !other)
38 return;
39
40 // Before either is read: one PxController::move() reports every shape
41 // it hits, and the handler for the first is free to delete a node the
42 // second one points at.
43 if (world->isNodeRemoved(other) || world->isNodeRemoved(trigger))
44 return;
45
46 if (!trigger->enableShapeHitCallback())
47 return;
48
49 QVector3D position = QPhysicsUtils::toQtType(physx::toVec3(hit.worldPos));
50 QVector3D impulse = QPhysicsUtils::toQtType(hit.dir * hit.length);
51 QVector3D normal = QPhysicsUtils::toQtType(hit.worldNormal);
52
53 emit trigger->shapeHit(other, position, impulse, normal);
54 }
55 void onControllerHit(const physx::PxControllersHit & /*hit*/) override { }
56 void onObstacleHit(const physx::PxControllerObstacleHit & /*hit*/) override { }
57
58private:
59 QPhysicsWorld *world = nullptr;
60};
61
63 : QAbstractPhysXNode(frontEnd)
64{
65}
66
68{
69 PHYSX_RELEASE(controller);
70 delete reportCallback;
71 reportCallback = nullptr;
73}
74
75void QPhysXCharacterController::init(QPhysicsWorld *world, QPhysXWorld * /*physX*/)
76{
77 Q_ASSERT(!controller);
78
79 auto *characterController = static_cast<QCharacterController *>(frontendNode);
80
81 auto shapes = characterController->getCollisionShapesList();
82 if (shapes.length() != 1) {
83 qWarning() << "CharacterController: invalid collision shapes list.";
84 return;
85 }
86 auto *capsule = qobject_cast<QCapsuleShape *>(shapes.first());
87 if (!capsule) {
88 qWarning() << "CharacterController: collision shape is not a capsule.";
89 return;
90 }
91 auto *mgr = world->controllerManager();
92 if (!mgr) {
93 qWarning() << "QtQuick3DPhysics internal error: missing controller manager.";
94 return;
95 }
96
98
99 const QVector3D scale = characterController->sceneScale();
100 const qreal heightScale = scale.y();
101 const qreal radiusScale = scale.x();
102 physx::PxCapsuleControllerDesc desc;
103 reportCallback = new ControllerCallback(world);
104 desc.reportCallback = reportCallback;
105 desc.radius = 0.5f * radiusScale * capsule->diameter();
106 desc.height = heightScale * capsule->height();
107 desc.stepOffset = 0.25f * desc.height; // TODO: API
108
109 desc.material = material;
110 const QVector3D pos = characterController->scenePosition();
111 desc.position = { pos.x(), pos.y(), pos.z() };
112 // Safe to static_cast since createController will always return a PxCapsuleController
113 // if desc is of type PxCapsuleControllerDesc
114 controller = static_cast<physx::PxCapsuleController *>(mgr->createController(desc));
115
116 if (!controller) {
117 qWarning() << "QtQuick3DPhysics internal error: could not create controller.";
118 return;
119 }
120
121 controller->setUserData(static_cast<void *>(frontendNode));
122
123 auto *actor = controller->getActor();
124 if (actor)
125 actor->userData = characterController;
126 else
127 qWarning() << "QtQuick3DPhysics internal error: CharacterController created without actor.";
128
129 // No collision shape in the userData here, unlike the shapes a body builds,
130 // which is how a trigger tells one body's pairs apart. A controller has
131 // exactly one shape, checked above, so its pairs are one per trigger shape
132 // either way, and pointing at the capsule would go stale: the controller's
133 // shape is never rebuilt, so swapping the capsule would re-report nothing.
134}
135
136void QPhysXCharacterController::sync(float deltaTime,
137 QHash<QQuick3DNode *, QMatrix4x4> & /*transformCache*/)
138{
139 if (controller == nullptr)
140 return;
141
142 auto *characterController = static_cast<QCharacterController *>(frontendNode);
143
144 // Update capsule height, radius, stepOffset
145 const auto &shapes = characterController->getCollisionShapesList();
146 auto capsule = shapes.length() == 1 ? qobject_cast<QCapsuleShape *>(shapes.front()) : nullptr;
147
148 if (shapes.length() != 1) {
149 qWarning() << "CharacterController: invalid collision shapes list.";
150 } else if (!capsule) {
151 qWarning() << "CharacterController: collision shape is not a capsule.";
152 } else {
153 const QVector3D sceneScale = characterController->sceneScale();
154 const qreal heightScale = sceneScale.y();
155 const qreal radiusScale = sceneScale.x();
156
157 // Update height
158 const float heightNew = heightScale * capsule->height();
159 // Update radius
160 const float radiusNew = 0.5f * radiusScale * capsule->diameter();
161
162 if (!qIsFinite(heightNew) || heightNew <= 0.f || !qIsFinite(radiusNew)
163 || radiusNew <= 0.f) {
164 qWarning() << "CharacterController: scaled capsule height/radius must be finite "
165 "and positive, ignoring.";
166 } else {
167 if (!qFuzzyCompare(controller->getHeight(), heightNew))
168 controller->resize(heightNew);
169 if (!qFuzzyCompare(controller->getRadius(), radiusNew))
170 controller->setRadius(radiusNew);
171 }
172 // Update stepOffset
173 const float stepOffsetNew = 0.25f * heightNew;
174 if (!qFuzzyCompare(controller->getStepOffset(), stepOffsetNew))
175 controller->setStepOffset(stepOffsetNew);
176 }
177
178 // update node from physX
179 QVector3D position = QPhysicsUtils::toQtType(physx::toVec3(controller->getPosition()));
180 const QQuick3DNode *parentNode = static_cast<QQuick3DNode *>(characterController->parentItem());
181 if (!parentNode) {
182 // then it is the same space
183 characterController->setPosition(position);
184 } else {
185 characterController->setPosition(parentNode->mapPositionFromScene(position));
186 }
187
188 // setPosition() ran the controller's bindings, which can delete it.
189 if (!frontendNode)
190 return;
191
192 QVector3D teleportPos;
193 bool teleport = characterController->getTeleport(teleportPos);
194 if (teleport) {
195 controller->setPosition({ teleportPos.x(), teleportPos.y(), teleportPos.z() });
196 } else if (deltaTime > 0) {
197 const auto displacement =
198 QPhysicsUtils::toPhysXType(characterController->getDisplacement(deltaTime));
199 auto collisions =
200 controller->move(displacement, displacement.magnitude() / 100, deltaTime, {});
201 // PxController::move() reports what it hits, and a handler for that
202 // can delete the controller.
203 if (!frontendNode)
204 return;
205 characterController->setCollisions(QCharacterController::Collisions(uint(collisions)));
206 }
207
208 // setCollisions() ran the controller's bindings, which can delete it.
209 if (!frontendNode)
210 return;
211
212 // Materials are shared between nodes, so a change of the material properties moves this
213 // node to another material, which the shape of the controller then has to be pointed at.
214 // The shape is created exclusive to the actor of the controller, so it stays writable.
215 if (updateMaterial() && material) {
216 physx::PxShape *shape = nullptr;
217 auto *actor = controller->getActor();
218 // The controller is created with an actor holding exactly one shape, its capsule
219 if (actor && actor->getShapes(&shape, 1) == 1)
220 shape->setMaterials(&material, 1);
221 }
222}
223
224QPhysicsMaterial *QPhysXCharacterController::qtMaterial() const
225{
226 return static_cast<QCharacterController *>(frontendNode)->physicsMaterial();
227}
228
230{
231 return true;
232}
233
238
240{
241 if (controller) {
242 controller->setUserData(nullptr);
243 if (auto *actor = controller->getActor())
244 actor->userData = nullptr;
245 }
246
248}
249
virtual void cleanup(QPhysXWorld *)
virtual void detachFrontend()
QPhysXCharacterController(QCharacterController *frontEnd)
QPhysicsMaterial * qtMaterial() const override
DebugDrawBodyType getDebugDrawBodyType() override
void init(QPhysicsWorld *world, QPhysXWorld *physX) override
void cleanup(QPhysXWorld *physX) override
void sync(float deltaTime, QHash< QQuick3DNode *, QMatrix4x4 > &transformCache) override
#define PHYSX_RELEASE(x)
DebugDrawBodyType
#define QT_BEGIN_NAMESPACE
#define QT_END_NAMESPACE