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
qcharactercontroller.cpp
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
7
8#include "physxnode/qphysxcharactercontroller_p.h"
9
11
12/*!
13 \qmltype CharacterController
14 \inqmlmodule QtQuick3D.Physics
15 \inherits PhysicsBody
16 \since 6.4
17 \brief Controls the motion of a character.
18
19 The CharacterController type controls the motion of a character.
20
21 A character is an entity that moves under external control, but is still constrained
22 by physical barriers and (optionally) subject to gravity. This is in contrast to
23 \l{DynamicRigidBody}{dynamic rigid bodies} which are either completely controlled by
24 the physics simulation (for non-kinematic bodies); or move exactly where placed,
25 regardless of barriers (for kinematic objects).
26
27 To control the motion of a character controller, set \l movement to the desired velocity.
28
29 For a first-person view, the camera is typically placed inside a character controller.
30
31 \note \l {PhysicsNode::collisionShapes}{collisionShapes} must be set to
32 a single \l {CapsuleShape}. No other shapes are supported.
33
34 \note The character controller is able to scale obstacles that are lower than one fourth of
35 the capsule shape's height.
36
37 \sa {Qt Quick 3D Physics Shapes, Bodies and Joints}
38*/
39
40/*!
41 \qmlproperty vector3d CharacterController::movement
42
43 This property defines the controlled motion of the character. This is the velocity the character
44 would move in the absence of gravity and without interacting with other physics objects.
45
46 This property does not reflect the actual velocity of the character. If the character is stuck
47 against terrain, the character can move slower than the speed defined by \c movement. Conversely, if the
48 character is in free fall, it may move much faster.
49
50 Default value: \c{(0, 0, 0)}
51*/
52
53/*!
54 \qmlproperty vector3d CharacterController::gravity
55
56 This property defines the gravitational acceleration that applies to the character.
57 For a character that walks on the ground, it should typically be set to
58 \l{PhysicsWorld::gravity}{PhysicsWorld.gravity}. A floating character that has movement
59 controls in three dimensions will normally have gravity \c{(0, 0, 0)}.
60
61 Default value: \c{(0, 0, 0)}.
62*/
63
64/*!
65 \qmlproperty bool CharacterController::midAirControl
66
67 This property defines whether the \l movement property has effect when the character is in free
68 fall. This is only relevant if \l gravity in not null. A value of \c true means that the
69 character will change direction in mid-air when \c movement changes. A value of \c false means that
70 the character will continue on its current trajectory until it hits another object.
71
72 Default value: \c true
73*/
74
75/*!
76 \qmlproperty Collisions CharacterController::collisions
77 \readonly
78
79 This property holds the current collision state of the character. It is either \c None for no
80 collision, or an OR combination of \c Side, \c Up, and \c Down:
81
82 \value CharacterController.None
83 The character is not touching anything. If gravity is non-null, this means that the
84 character is in free fall.
85 \value CharacterController.Side
86 The character is touching something on its side.
87 \value CharacterController.Up
88 The character is touching something above it.
89 \value CharacterController.Down
90 The character is touching something below it. In standard gravity, this means
91 that the character is on the ground.
92
93 \note The directions are defined relative to standard gravity: \c Up is always along the
94 positive y-axis, regardless of the value of \l {gravity}{CharacterController.gravity}
95 or \l{PhysicsWorld::gravity}{PhysicsWorld.gravity}
96*/
97
98/*!
99 \qmlproperty bool CharacterController::enableShapeHitCallback
100 \since 6.6
101
102 This property enables/disables the \l {CharacterController::shapeHit} callback for this
103 character controller.
104
105 Default value: \c{false}
106*/
107
108/*!
109 \qmlmethod void CharacterController::teleport(vector3d position)
110 Immediately move the character to \a position without checking for collisions.
111 The caller is responsible for avoiding overlap with static objects.
112*/
113
114/*!
115 \qmlsignal CharacterController::shapeHit(PhysicsNode *body, vector3D position, vector3D impulse,
116 vector3D normal)
117 \since 6.6
118
119 This signal is emitted when \l {CharacterController::}{movement} has been
120 called and it would result
121 in a collision with a \l {DynamicRigidBody} or a \l {StaticRigidBody} and
122 \l {CharacterController::} {enableShapeHitCallback} is set to \c true.
123 The parameters \a body, \a position, \a impulse and \a normal contain the body, position,
124 impulse force and normal for the contact point.
125*/
126
127QCharacterController::QCharacterController() = default;
128
129const QVector3D &QCharacterController::movement() const
130{
131 return m_movement;
132}
133
134void QCharacterController::setMovement(const QVector3D &newMovement)
135{
136 if (!QPhysicsUtils::isFinite(newMovement)) {
137 qWarning() << "CharacterController: movement must be finite, ignoring" << newMovement;
138 return;
139 }
140
141 if (m_movement == newMovement)
142 return;
143 m_movement = newMovement;
144 emit movementChanged();
145}
146
147const QVector3D &QCharacterController::gravity() const
148{
149 return m_gravity;
150}
151
152void QCharacterController::setGravity(const QVector3D &newGravity)
153{
154 if (!QPhysicsUtils::isFinite(newGravity)) {
155 qWarning() << "CharacterController: gravity must be finite, ignoring" << newGravity;
156 return;
157 }
158
159 if (m_gravity == newGravity)
160 return;
161 m_gravity = newGravity;
162 emit gravityChanged();
163}
164
165// Calculate move based on movement/gravity
166
167QVector3D QCharacterController::getDisplacement(float deltaTime)
168{
169 // Start with basic movement, assuming no other factors
170 QVector3D displacement = sceneRotation() * m_movement * deltaTime;
171
172 // modified based on gravity
173 const auto g = m_gravity;
174 if (!g.isNull()) {
175
176 // Avoid "spider mode": we are also supposed to be in free fall if gravity
177 // is pointing away from a surface we are touching. I.e. we are NOT in free
178 // fall only if gravity has a component in the direction of one of the collisions.
179 // Also: if we have "upwards" free fall velocity, that motion needs to stop
180 // when we hit the "ceiling"; i.e we are not in free fall at the moment of impact.
181 auto isGrounded = [this](){
182 if (m_collisions == Collision::None)
183 return false;
184
185 // Standard gravity case first
186 if (m_gravity.y() < 0) {
187 if (m_collisions & Collision::Down)
188 return true; // We land on the ground
189 if ((m_collisions & Collision::Up) && m_freeFallVelocity.y() > 0)
190 return true; // We bump our head on the way up
191 }
192
193 // Inverse gravity next: exactly the opposite
194 if (m_gravity.y() > 0) {
195 if (m_collisions & Collision::Up)
196 return true;
197 if ((m_collisions & Collision::Down) && m_freeFallVelocity.y() < 0)
198 return true;
199 }
200
201 // The sideways gravity case can't be perfectly handled since we don't
202 // know the direction of sideway contacts. We could in theory inspect
203 // the mesh, but that is far too complex for an extremely marginal use case.
204
205 if ((m_gravity.x() != 0 || m_gravity.z() != 0) && m_collisions & Collision::Side)
206 return true;
207
208 return false;
209 };
210
211 bool freeFalling = !isGrounded();
212 if (freeFalling) {
213 if (!m_midAirControl)
214 displacement = {}; // Ignore the movement() controls in true free fall
215
216 displacement += m_freeFallVelocity * deltaTime;
217 m_freeFallVelocity += g * deltaTime;
218 } else {
219 m_freeFallVelocity = displacement / deltaTime + g * deltaTime;
220 if (m_midAirControl) // free fall only straight down
221 m_freeFallVelocity =
222 QVector3D::dotProduct(m_freeFallVelocity, g.normalized()) * g.normalized();
223 }
224 const QVector3D gravityAcceleration = 0.5 * deltaTime * deltaTime * g;
225 displacement += gravityAcceleration; // always add gravitational acceleration, in case we start
226 // to fall. If we don't, PhysX will move us back to the ground.
227 }
228
229 return displacement;
230}
231
232bool QCharacterController::midAirControl() const
233{
234 return m_midAirControl;
235}
236
237void QCharacterController::setMidAirControl(bool newMidAirControl)
238{
239 if (m_midAirControl == newMidAirControl)
240 return;
241 m_midAirControl = newMidAirControl;
242 emit midAirControlChanged();
243}
244
245void QCharacterController::teleport(const QVector3D &position)
246{
247 if (!QPhysicsUtils::isFinite(position)) {
248 qWarning() << "CharacterController: teleport() position must be finite, ignoring"
249 << position;
250 return;
251 }
252
253 m_teleport = true;
254 m_teleportPosition = position;
255 m_freeFallVelocity = {};
256}
257
258bool QCharacterController::getTeleport(QVector3D &position)
259{
260 if (m_teleport) {
261 position = m_teleportPosition;
262 m_teleport = false;
263 return true;
264 }
265 return false;
266}
267
268const QCharacterController::Collisions &QCharacterController::collisions() const
269{
270 return m_collisions;
271}
272
273void QCharacterController::setCollisions(const Collisions &newCollisions)
274{
275 if (m_collisions == newCollisions)
276 return;
277 m_collisions = newCollisions;
278 emit collisionsChanged();
279}
280
281bool QCharacterController::enableShapeHitCallback() const
282{
283 return m_enableShapeHitCallback;
284}
285
286QAbstractPhysXNode *QCharacterController::createPhysXBackend()
287{
288 return new QPhysXCharacterController(this);
289}
290
291void QCharacterController::setEnableShapeHitCallback(bool newEnableShapeHitCallback)
292{
293 if (m_enableShapeHitCallback == newEnableShapeHitCallback)
294 return;
295 m_enableShapeHitCallback = newEnableShapeHitCallback;
296 emit enableShapeHitCallbackChanged();
297}
298
#define QT_BEGIN_NAMESPACE
#define QT_END_NAMESPACE