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
qphysxdynamicbody.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 "PxPhysics.h"
8#include "PxRigidDynamic.h"
9
16
17#include <QtGui/qquaternion.h>
18
20
21static void processCommandQueue(QQueue<QPhysicsCommand *> &commandQueue,
22 const QDynamicRigidBody &rigidBody, physx::PxRigidBody &body)
23{
24 for (auto command : std::as_const(commandQueue)) {
25 command->execute(rigidBody, body);
26 delete command;
27 }
28
29 commandQueue.clear();
30}
31
32static QMatrix4x4 calculateKinematicNodeTransform(QQuick3DNode *node,
33 QHash<QQuick3DNode *, QMatrix4x4> &transformCache)
34{
35 // already calculated transform
36 if (transformCache.contains(node))
37 return transformCache[node];
38
39 QMatrix4x4 localTransform;
40
41 // DynamicRigidBody vs StaticRigidBody use different values for calculating the local transform
42 if (auto drb = qobject_cast<const QDynamicRigidBody *>(node); drb != nullptr) {
43 if (!drb->isKinematic()) {
44 qWarning() << "Non-kinematic body as a parent of a kinematic body is unsupported";
45 }
46 localTransform = QSSGRenderNode::calculateTransformMatrix(
47 drb->kinematicPosition(), drb->scale(), drb->kinematicPivot(),
48 drb->kinematicRotation());
49 } else {
50 localTransform = QSSGRenderNode::calculateTransformMatrix(node->position(), node->scale(),
51 node->pivot(), node->rotation());
52 }
53
54 QQuick3DNode *parent = node->parentNode();
55 if (!parent) // no parent, local transform is scene transform
56 return localTransform;
57
58 // calculate the parent scene transform and apply the nodes local transform
59 QMatrix4x4 parentTransform = calculateKinematicNodeTransform(parent, transformCache);
60 QMatrix4x4 sceneTransform = parentTransform * localTransform;
61
62 transformCache[node] = sceneTransform;
63 return sceneTransform;
64}
65
66static physx::PxRigidDynamicLockFlags getLockFlags(QDynamicRigidBody *body)
67{
68 const auto lockAngular = body->angularAxisLock();
69 const auto lockLinear = body->linearAxisLock();
70 const int flags = (lockAngular & QDynamicRigidBody::AxisLock::LockX
71 ? physx::PxRigidDynamicLockFlag::eLOCK_ANGULAR_X
72 : 0)
73 | (lockAngular & QDynamicRigidBody::AxisLock::LockY
74 ? physx::PxRigidDynamicLockFlag::eLOCK_ANGULAR_Y
75 : 0)
76 | (lockAngular & QDynamicRigidBody::AxisLock::LockZ
77 ? physx::PxRigidDynamicLockFlag::eLOCK_ANGULAR_Z
78 : 0)
79 | (lockLinear & QDynamicRigidBody::AxisLock::LockX
80 ? physx::PxRigidDynamicLockFlag::eLOCK_LINEAR_X
81 : 0)
82 | (lockLinear & QDynamicRigidBody::AxisLock::LockY
83 ? physx::PxRigidDynamicLockFlag::eLOCK_LINEAR_Y
84 : 0)
85 | (lockLinear & QDynamicRigidBody::AxisLock::LockZ
86 ? physx::PxRigidDynamicLockFlag::eLOCK_LINEAR_Z
87 : 0);
88 return static_cast<physx::PxRigidDynamicLockFlags>(flags);
89}
90
91static physx::PxTransform getPhysXWorldTransform(const QMatrix4x4 transform)
92{
93 auto rotationMatrix = transform;
94 QSSGUtils::mat44::normalize(rotationMatrix);
95 auto rotation =
96 QQuaternion::fromRotationMatrix(QSSGUtils::mat44::getUpper3x3(rotationMatrix)).normalized();
97 const QVector3D worldPosition = QSSGUtils::mat44::getPosition(transform);
98 return physx::PxTransform(QPhysicsUtils::toPhysXType(worldPosition),
99 QPhysicsUtils::toPhysXType(rotation));
100}
101
102QPhysXDynamicBody::QPhysXDynamicBody(QDynamicRigidBody *frontEnd) : QPhysXRigidBody(frontEnd) { }
103
105{
106 auto *dynamicRigidBody = static_cast<QDynamicRigidBody *>(frontendNode);
107 if (!dynamicRigidBody->isKinematic()) {
109 return;
110 }
111
112 // A kinematic body is never moved by setGlobalPose()/the plain position/rotation
113 // properties; it's driven by setKinematicTarget() using kinematicPosition()/
114 // kinematicRotation() (see sync() below). If the actor were created at the plain,
115 // usually-unset position/rotation instead, the first simulated step would snap it
116 // over to its real kinematic pose in one step, which can violently yank anything
117 // jointed to it. So the initial pose must already match the kinematic one.
118 QHash<QQuick3DNode *, QMatrix4x4> transformCache;
119 const QMatrix4x4 transform = calculateKinematicNodeTransform(dynamicRigidBody, transformCache);
120 physx::PxTransform trf = getPhysXWorldTransform(transform);
121 if (!trf.isSane()) {
122 qWarning() << "DynamicRigidBody: kinematic position/rotation is not finite, using "
123 "identity instead.";
124 trf = physx::PxTransform(physx::PxIdentity);
125 }
127 actor = s_physx.physics->createRigidDynamic(trf);
128}
129
131{
132 auto *dynamicRigidBody = static_cast<QDynamicRigidBody *>(frontendNode);
133 return dynamicRigidBody->isSleeping() ? DebugDrawBodyType::DynamicSleeping
135}
136
137void QPhysXDynamicBody::sync(float deltaTime, QHash<QQuick3DNode *, QMatrix4x4> &transformCache)
138{
139 auto *dynamicRigidBody = static_cast<QDynamicRigidBody *>(frontendNode);
140 // first update front end node from physx simulation
141 dynamicRigidBody->updateFromPhysicsTransform(actor->getGlobalPose());
142
143 auto *dynamicActor = static_cast<physx::PxRigidDynamic *>(actor);
144 processCommandQueue(dynamicRigidBody->commandQueue(), *dynamicRigidBody, *dynamicActor);
145 if (dynamicRigidBody->isKinematic()) {
146 // Since this is a kinematic body we need to calculate the transform by hand and since
147 // bodies can occur in other bodies we need to calculate the tranform recursively for all
148 // parents. To save some computation we cache these transforms in 'transformCache'.
149 QMatrix4x4 transform = calculateKinematicNodeTransform(dynamicRigidBody, transformCache);
150 const physx::PxTransform worldTransform = getPhysXWorldTransform(transform);
151 if (worldTransform.isSane()) {
152 dynamicActor->setKinematicTarget(worldTransform);
153 } else {
154 qWarning() << "DynamicRigidBody: kinematic transform is not finite, keeping "
155 "previous target.";
156 }
157 } else {
158 dynamicActor->setRigidDynamicLockFlags(getLockFlags(dynamicRigidBody));
159 }
160
161 const bool disabledPrevious = actor->getActorFlags() & physx::PxActorFlag::eDISABLE_SIMULATION;
162 const bool disabled = !dynamicRigidBody->simulationEnabled();
163 if (disabled != disabledPrevious) {
164 actor->setActorFlag(physx::PxActorFlag::eDISABLE_SIMULATION, disabled);
165 if (!disabled && !dynamicRigidBody->isKinematic())
166 dynamicActor->wakeUp();
167 }
168
169 dynamicRigidBody->setIsSleeping(dynamicActor->isSleeping());
170
171 QPhysXActorBody::sync(deltaTime, transformCache);
172}
173
174void QPhysXDynamicBody::rebuildDirtyShapes(QPhysicsWorld *world, QPhysXWorld *physX)
175{
176 if (!shapesDirty())
177 return;
178
179 buildShapes(physX);
180
181 QDynamicRigidBody *drb = static_cast<QDynamicRigidBody *>(frontendNode);
182
183 // Density must be set after shapes so the inertia tensor is set
184 if (!drb->hasStaticShapes()) {
185 // Body with only dynamic shapes, set/calculate mass
186 QPhysicsCommand *command = nullptr;
187 switch (drb->massMode()) {
188 case QDynamicRigidBody::MassMode::DefaultDensity: {
189 command = new QPhysicsCommandSetDensity(world->defaultDensity());
190 break;
191 }
192 case QDynamicRigidBody::MassMode::CustomDensity: {
193 command = new QPhysicsCommandSetDensity(drb->density());
194 break;
195 }
196 case QDynamicRigidBody::MassMode::Mass: {
197 const float mass = qMax(drb->mass(), 0.f);
198 command = new QPhysicsCommandSetMass(mass);
199 break;
200 }
201 case QDynamicRigidBody::MassMode::MassAndInertiaTensor: {
202 const float mass = qMax(drb->mass(), 0.f);
203 command = new QPhysicsCommandSetMassAndInertiaTensor(mass, drb->inertiaTensor());
204 break;
205 }
206 case QDynamicRigidBody::MassMode::MassAndInertiaMatrix: {
207 const float mass = qMax(drb->mass(), 0.f);
208 command = new QPhysicsCommandSetMassAndInertiaMatrix(mass, drb->inertiaMatrix());
209 break;
210 }
211 }
212
213 drb->commandQueue().enqueue(command);
214 } else if (!drb->isKinematic()) {
215 // Body with static shapes that is not kinematic, this is disallowed
216 qWarning() << "Cannot make body containing trimesh/heightfield/plane non-kinematic, "
217 "forcing kinematic.";
218 drb->setIsKinematic(true);
219 }
220
221 const bool isKinematic = drb->isKinematic();
222 QDynamicRigidBody::CCDType ccd = drb->ccd();
223
224 auto *dynamicBody = static_cast<physx::PxRigidDynamic *>(actor);
225
226 // Clear CCD before flipping kinematic mode: PhysX rejects sweep-based CCD on
227 // kinematic bodies, so leaving a stale CCD flag set while eKINEMATIC changes
228 // would trigger a spurious warning regardless of transition direction.
229 dynamicBody->setRigidBodyFlag(physx::PxRigidBodyFlag::eENABLE_CCD, false); // Sweep-based
230 dynamicBody->setRigidBodyFlag(physx::PxRigidBodyFlag::eENABLE_SPECULATIVE_CCD, false);
231 dynamicBody->setRigidBodyFlag(physx::PxRigidBodyFlag::eKINEMATIC, isKinematic);
232
233 switch (ccd) {
234 case QDynamicRigidBody::CCDType::SweepBasedCCD: {
235 // Kinematic bodies only support speculative CCD
236 dynamicBody->setRigidBodyFlag(physx::PxRigidBodyFlag::eENABLE_CCD, // Sweep-based
237 !isKinematic);
238 dynamicBody->setRigidBodyFlag(physx::PxRigidBodyFlag::eENABLE_SPECULATIVE_CCD, isKinematic);
239 break;
240 }
241
242 case QDynamicRigidBody::CCDType::SpeculativeCCD: {
243 dynamicBody->setRigidBodyFlag(physx::PxRigidBodyFlag::eENABLE_SPECULATIVE_CCD, true);
244 break;
245 }
246
247 case QDynamicRigidBody::CCDType::None: {
248 if (world->enableCCD()) {
249 // Kinematic bodies only support speculative CCD
250 dynamicBody->setRigidBodyFlag(physx::PxRigidBodyFlag::eENABLE_CCD, // Sweep-based
251 !isKinematic);
252 dynamicBody->setRigidBodyFlag(physx::PxRigidBodyFlag::eENABLE_SPECULATIVE_CCD,
253 isKinematic);
254 }
255 break;
256 }
257 }
258
259 setShapesDirty(false);
260}
261
263{
264 QDynamicRigidBody *rigidBody = static_cast<QDynamicRigidBody *>(frontendNode);
265 rigidBody->updateDefaultDensity(density);
266}
267
void setShapesDirty(bool dirty)
void buildShapes(QPhysXWorld *physX)
virtual void createActor(QPhysXWorld *physX)
void updateDefaultDensity(float density) override
QPhysXDynamicBody(QDynamicRigidBody *frontEnd)
DebugDrawBodyType getDebugDrawBodyType() override
void sync(float deltaTime, QHash< QQuick3DNode *, QMatrix4x4 > &transformCache) override
void rebuildDirtyShapes(QPhysicsWorld *world, QPhysXWorld *physX) override
void createActor(QPhysXWorld *physX) override
QPhysicsCommandSetMass(float inMass)
DebugDrawBodyType
static QMatrix4x4 calculateKinematicNodeTransform(QQuick3DNode *node, QHash< QQuick3DNode *, QMatrix4x4 > &transformCache)
static physx::PxTransform getPhysXWorldTransform(const QMatrix4x4 transform)
static physx::PxRigidDynamicLockFlags getLockFlags(QDynamicRigidBody *body)
static QT_BEGIN_NAMESPACE void processCommandQueue(QQueue< QPhysicsCommand * > &commandQueue, const QDynamicRigidBody &rigidBody, physx::PxRigidBody &body)
#define QT_BEGIN_NAMESPACE
#define QT_END_NAMESPACE
static StaticPhysXObjects & getReference()