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 "characterkinematic/PxController.h"
9#include "characterkinematic/PxControllerManager.h"
10#include "characterkinematic/PxCapsuleController.h"
11
16
17#define PHYSX_RELEASE(x)
18 if (x != nullptr) {
19 x->release();
20 x = nullptr;
21 }
22
24
25class ControllerCallback : public physx::PxUserControllerHitReport
26{
27public:
28 ControllerCallback(QPhysicsWorld *worldIn) : world(worldIn) { }
29
30 void onShapeHit(const physx::PxControllerShapeHit &hit) override
31 {
32 QMutexLocker locker(&world->m_removedPhysicsNodesMutex);
33
34 QAbstractPhysicsNode *other = static_cast<QAbstractPhysicsNode *>(hit.actor->userData);
35 QCharacterController *trigger =
36 static_cast<QCharacterController *>(hit.controller->getUserData());
37
38 if (!trigger || !other || !trigger->enableShapeHitCallback())
39 return;
40
41 QVector3D position = QPhysicsUtils::toQtType(physx::toVec3(hit.worldPos));
42 QVector3D impulse = QPhysicsUtils::toQtType(hit.dir * hit.length);
43 QVector3D normal = QPhysicsUtils::toQtType(hit.worldNormal);
44
45 emit trigger->shapeHit(other, position, impulse, normal);
46 }
47 void onControllerHit(const physx::PxControllersHit & /*hit*/) override { }
48 void onObstacleHit(const physx::PxControllerObstacleHit & /*hit*/) override { }
49
50private:
51 QPhysicsWorld *world = nullptr;
52};
53
55 : QAbstractPhysXNode(frontEnd)
56{
57}
58
60{
61 PHYSX_RELEASE(controller);
62 delete reportCallback;
63 reportCallback = nullptr;
65}
66
67void QPhysXCharacterController::init(QPhysicsWorld *world, QPhysXWorld *physX)
68{
69 Q_ASSERT(!controller);
70
71 auto *characterController = static_cast<QCharacterController *>(frontendNode);
72
73 auto shapes = characterController->getCollisionShapesList();
74 if (shapes.length() != 1) {
75 qWarning() << "CharacterController: invalid collision shapes list.";
76 return;
77 }
78 auto *capsule = qobject_cast<QCapsuleShape *>(shapes.first());
79 if (!capsule) {
80 qWarning() << "CharacterController: collision shape is not a capsule.";
81 return;
82 }
83 auto *mgr = world->controllerManager();
84 if (!mgr) {
85 qWarning() << "QtQuick3DPhysics internal error: missing controller manager.";
86 return;
87 }
88
90
91 const QVector3D scale = characterController->sceneScale();
92 const qreal heightScale = scale.y();
93 const qreal radiusScale = scale.x();
94 physx::PxCapsuleControllerDesc desc;
95 reportCallback = new ControllerCallback(world);
96 desc.reportCallback = reportCallback;
97 desc.radius = 0.5f * radiusScale * capsule->diameter();
98 desc.height = heightScale * capsule->height();
99 desc.stepOffset = 0.25f * desc.height; // TODO: API
100
101 desc.material = material;
102 const QVector3D pos = characterController->scenePosition();
103 desc.position = { pos.x(), pos.y(), pos.z() };
104 // Safe to static_cast since createController will always return a PxCapsuleController
105 // if desc is of type PxCapsuleControllerDesc
106 controller = static_cast<physx::PxCapsuleController *>(mgr->createController(desc));
107
108 if (!controller) {
109 qWarning() << "QtQuick3DPhysics internal error: could not create controller.";
110 return;
111 }
112
113 controller->setUserData(static_cast<void *>(frontendNode));
114
115 auto *actor = controller->getActor();
116 if (actor)
117 actor->userData = characterController;
118 else
119 qWarning() << "QtQuick3DPhysics internal error: CharacterController created without actor.";
120}
121
122void QPhysXCharacterController::sync(float deltaTime,
123 QHash<QQuick3DNode *, QMatrix4x4> & /*transformCache*/)
124{
125 if (controller == nullptr)
126 return;
127
128 auto *characterController = static_cast<QCharacterController *>(frontendNode);
129
130 // Update capsule height, radius, stepOffset
131 const auto &shapes = characterController->getCollisionShapesList();
132 auto capsule = shapes.length() == 1 ? qobject_cast<QCapsuleShape *>(shapes.front()) : nullptr;
133
134 if (shapes.length() != 1) {
135 qWarning() << "CharacterController: invalid collision shapes list.";
136 } else if (!capsule) {
137 qWarning() << "CharacterController: collision shape is not a capsule.";
138 } else {
139 const QVector3D sceneScale = characterController->sceneScale();
140 const qreal heightScale = sceneScale.y();
141 const qreal radiusScale = sceneScale.x();
142
143 // Update height
144 const float heightNew = heightScale * capsule->height();
145 if (!qFuzzyCompare(controller->getHeight(), heightNew))
146 controller->resize(heightNew);
147 // Update radius
148 const float radiusNew = 0.5f * radiusScale * capsule->diameter();
149 if (!qFuzzyCompare(controller->getRadius(), radiusNew))
150 controller->setRadius(radiusNew);
151 // Update stepOffset
152 const float stepOffsetNew = 0.25f * heightNew;
153 if (!qFuzzyCompare(controller->getStepOffset(), stepOffsetNew))
154 controller->setStepOffset(stepOffsetNew);
155 }
156
157 // update node from physX
158 QVector3D position = QPhysicsUtils::toQtType(physx::toVec3(controller->getPosition()));
159 const QQuick3DNode *parentNode = static_cast<QQuick3DNode *>(characterController->parentItem());
160 if (!parentNode) {
161 // then it is the same space
162 characterController->setPosition(position);
163 } else {
164 characterController->setPosition(parentNode->mapPositionFromScene(position));
165 }
166
167 QVector3D teleportPos;
168 bool teleport = characterController->getTeleport(teleportPos);
169 if (teleport) {
170 controller->setPosition({ teleportPos.x(), teleportPos.y(), teleportPos.z() });
171 } else if (deltaTime > 0) {
172 const auto displacement =
173 QPhysicsUtils::toPhysXType(characterController->getDisplacement(deltaTime));
174 auto collisions =
175 controller->move(displacement, displacement.magnitude() / 100, deltaTime, {});
176 characterController->setCollisions(QCharacterController::Collisions(uint(collisions)));
177 }
178 // QCharacterController has a material property, but we don't inherit from
179 // QPhysXMaterialBody, so we create the material manually in init()
180 // TODO: handle material changes
181}
182
184{
186 physX, static_cast<QCharacterController *>(frontendNode)->physicsMaterial());
187}
188
190{
191 return true;
192}
193
198
virtual void cleanup(QPhysXWorld *)
void createMaterialFromQtMaterial(QPhysXWorld *physX, QPhysicsMaterial *qtMaterial)
QPhysXCharacterController(QCharacterController *frontEnd)
DebugDrawBodyType getDebugDrawBodyType() override
void init(QPhysicsWorld *world, QPhysXWorld *physX) override
void cleanup(QPhysXWorld *physX) override
void createMaterial(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