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