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