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 QMutexLocker locker(&world->m_removedPhysicsNodesMutex);
34
35 QAbstractPhysicsNode *other = static_cast<QAbstractPhysicsNode *>(hit.actor->userData);
36 QCharacterController *trigger =
37 static_cast<QCharacterController *>(hit.controller->getUserData());
38
39 if (!trigger || !other || !trigger->enableShapeHitCallback())
40 return;
41
42 if (world->isNodeRemoved(other) || world->isNodeRemoved(trigger))
43 return;
44
45 QVector3D position = QPhysicsUtils::toQtType(physx::toVec3(hit.worldPos));
46 QVector3D impulse = QPhysicsUtils::toQtType(hit.dir * hit.length);
47 QVector3D normal = QPhysicsUtils::toQtType(hit.worldNormal);
48
49 emit trigger->shapeHit(other, position, impulse, normal);
50 }
51 void onControllerHit(const physx::PxControllersHit & /*hit*/) override { }
52 void onObstacleHit(const physx::PxControllerObstacleHit & /*hit*/) override { }
53
54private:
55 QPhysicsWorld *world = nullptr;
56};
57
59 : QAbstractPhysXNode(frontEnd)
60{
61}
62
64{
65 PHYSX_RELEASE(controller);
66 delete reportCallback;
67 reportCallback = nullptr;
69}
70
71void QPhysXCharacterController::init(QPhysicsWorld *world, QPhysXWorld * /*physX*/)
72{
73 Q_ASSERT(!controller);
74
75 auto *characterController = static_cast<QCharacterController *>(frontendNode);
76
77 auto shapes = characterController->getCollisionShapesList();
78 if (shapes.length() != 1) {
79 qWarning() << "CharacterController: invalid collision shapes list.";
80 return;
81 }
82 auto *capsule = qobject_cast<QCapsuleShape *>(shapes.first());
83 if (!capsule) {
84 qWarning() << "CharacterController: collision shape is not a capsule.";
85 return;
86 }
87 auto *mgr = world->controllerManager();
88 if (!mgr) {
89 qWarning() << "QtQuick3DPhysics internal error: missing controller manager.";
90 return;
91 }
92
94
95 const QVector3D scale = characterController->sceneScale();
96 const qreal heightScale = scale.y();
97 const qreal radiusScale = scale.x();
98 physx::PxCapsuleControllerDesc desc;
99 reportCallback = new ControllerCallback(world);
100 desc.reportCallback = reportCallback;
101 desc.radius = 0.5f * radiusScale * capsule->diameter();
102 desc.height = heightScale * capsule->height();
103 desc.stepOffset = 0.25f * desc.height; // TODO: API
104
105 desc.material = material;
106 const QVector3D pos = characterController->scenePosition();
107 desc.position = { pos.x(), pos.y(), pos.z() };
108 // Safe to static_cast since createController will always return a PxCapsuleController
109 // if desc is of type PxCapsuleControllerDesc
110 controller = static_cast<physx::PxCapsuleController *>(mgr->createController(desc));
111
112 if (!controller) {
113 qWarning() << "QtQuick3DPhysics internal error: could not create controller.";
114 return;
115 }
116
117 controller->setUserData(static_cast<void *>(frontendNode));
118
119 auto *actor = controller->getActor();
120 if (actor)
121 actor->userData = characterController;
122 else
123 qWarning() << "QtQuick3DPhysics internal error: CharacterController created without actor.";
124}
125
126void QPhysXCharacterController::sync(float deltaTime,
127 QHash<QQuick3DNode *, QMatrix4x4> & /*transformCache*/)
128{
129 if (controller == nullptr)
130 return;
131
132 auto *characterController = static_cast<QCharacterController *>(frontendNode);
133
134 // Update capsule height, radius, stepOffset
135 const auto &shapes = characterController->getCollisionShapesList();
136 auto capsule = shapes.length() == 1 ? qobject_cast<QCapsuleShape *>(shapes.front()) : nullptr;
137
138 if (shapes.length() != 1) {
139 qWarning() << "CharacterController: invalid collision shapes list.";
140 } else if (!capsule) {
141 qWarning() << "CharacterController: collision shape is not a capsule.";
142 } else {
143 const QVector3D sceneScale = characterController->sceneScale();
144 const qreal heightScale = sceneScale.y();
145 const qreal radiusScale = sceneScale.x();
146
147 // Update height
148 const float heightNew = heightScale * capsule->height();
149 // Update radius
150 const float radiusNew = 0.5f * radiusScale * capsule->diameter();
151
152 if (!qIsFinite(heightNew) || heightNew <= 0.f || !qIsFinite(radiusNew)
153 || radiusNew <= 0.f) {
154 qWarning() << "CharacterController: scaled capsule height/radius must be finite "
155 "and positive, ignoring.";
156 } else {
157 if (!qFuzzyCompare(controller->getHeight(), heightNew))
158 controller->resize(heightNew);
159 if (!qFuzzyCompare(controller->getRadius(), radiusNew))
160 controller->setRadius(radiusNew);
161 }
162 // Update stepOffset
163 const float stepOffsetNew = 0.25f * heightNew;
164 if (!qFuzzyCompare(controller->getStepOffset(), stepOffsetNew))
165 controller->setStepOffset(stepOffsetNew);
166 }
167
168 // update node from physX
169 QVector3D position = QPhysicsUtils::toQtType(physx::toVec3(controller->getPosition()));
170 const QQuick3DNode *parentNode = static_cast<QQuick3DNode *>(characterController->parentItem());
171 if (!parentNode) {
172 // then it is the same space
173 characterController->setPosition(position);
174 } else {
175 characterController->setPosition(parentNode->mapPositionFromScene(position));
176 }
177
178 QVector3D teleportPos;
179 bool teleport = characterController->getTeleport(teleportPos);
180 if (teleport) {
181 controller->setPosition({ teleportPos.x(), teleportPos.y(), teleportPos.z() });
182 } else if (deltaTime > 0) {
183 const auto displacement =
184 QPhysicsUtils::toPhysXType(characterController->getDisplacement(deltaTime));
185 auto collisions =
186 controller->move(displacement, displacement.magnitude() / 100, deltaTime, {});
187 characterController->setCollisions(QCharacterController::Collisions(uint(collisions)));
188 }
189 // Materials are shared between nodes, so a change of the material properties moves this
190 // node to another material, which the shape of the controller then has to be pointed at.
191 // The shape is created exclusive to the actor of the controller, so it stays writable.
192 if (updateMaterial() && material) {
193 physx::PxShape *shape = nullptr;
194 auto *actor = controller->getActor();
195 // The controller is created with an actor holding exactly one shape, its capsule
196 if (actor && actor->getShapes(&shape, 1) == 1)
197 shape->setMaterials(&material, 1);
198 }
199}
200
201QPhysicsMaterial *QPhysXCharacterController::qtMaterial() const
202{
203 return static_cast<QCharacterController *>(frontendNode)->physicsMaterial();
204}
205
207{
208 return true;
209}
210
215
virtual void cleanup(QPhysXWorld *)
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