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::PxRigidDynamic &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 // The writes in updateFromPhysicsTransform() ran the body's bindings,
144 // which can delete it.
145 if (!frontendNode)
146 return;
147
148 auto *dynamicActor = static_cast<physx::PxRigidDynamic *>(actor);
149 processCommandQueue(dynamicRigidBody->commandQueue(), *dynamicRigidBody, *dynamicActor);
150
151 const bool disabledPrevious = actor->getActorFlags() & physx::PxActorFlag::eDISABLE_SIMULATION;
152 const bool disabled = !dynamicRigidBody->simulationEnabled();
153
154 // A body whose simulation is disabled has no simulation object behind it, and
155 // PxRigidDynamic::setKinematicTarget() dereferences that object without checking, so it
156 // must not be called for such a body. Nothing is lost by skipping it: the target is
157 // recomputed and set again on the first frame the body takes part in the simulation.
158 if (dynamicRigidBody->isKinematic() && !disabled && !disabledPrevious) {
159 // Since this is a kinematic body we need to calculate the transform by hand and since
160 // bodies can occur in other bodies we need to calculate the tranform recursively for all
161 // parents. To save some computation we cache these transforms in 'transformCache'.
162 QMatrix4x4 transform = calculateKinematicNodeTransform(dynamicRigidBody, transformCache);
163 const physx::PxTransform worldTransform = getPhysXWorldTransform(transform);
164 if (worldTransform.isSane()) {
165 dynamicActor->setKinematicTarget(worldTransform);
166 } else {
167 qWarning() << "DynamicRigidBody: kinematic transform is not finite, keeping "
168 "previous target.";
169 }
170 } else if (!dynamicRigidBody->isKinematic()) {
171 dynamicActor->setRigidDynamicLockFlags(getLockFlags(dynamicRigidBody));
172 }
173
174 if (disabled != disabledPrevious) {
175 actor->setActorFlag(physx::PxActorFlag::eDISABLE_SIMULATION, disabled);
176 if (!disabled && !dynamicRigidBody->isKinematic())
177 dynamicActor->wakeUp();
178 }
179
180 // Read once the queued commands and the simulation flag are applied, so that a velocity set
181 // for this frame is reported in it, and a body disabled in it reads zero, as it also reads
182 // as sleeping.
183 dynamicRigidBody->updateLinearVelocity(
184 QPhysicsUtils::toQtType(dynamicActor->getLinearVelocity()));
185 dynamicRigidBody->updateAngularVelocity(
186 QPhysicsUtils::toQtType(dynamicActor->getAngularVelocity()));
187 dynamicRigidBody->setIsSleeping(dynamicActor->isSleeping());
188
189 // The velocity and sleeping updates ran the body's bindings, which can delete the body.
190 if (!frontendNode)
191 return;
192
193 QPhysXActorBody::sync(deltaTime, transformCache);
194}
195
196void QPhysXDynamicBody::rebuildDirtyShapes(QPhysicsWorld *world, QPhysXWorld *physX)
197{
198 if (!shapesDirty())
199 return;
200
201 QDynamicRigidBody *drb = static_cast<QDynamicRigidBody *>(frontendNode);
202
203 // Before buildShapes(), since setIsKinematic() runs the body's bindings and
204 // can delete it, which would leave the shapes half built.
205 if (drb->hasStaticShapes() && !drb->isKinematic()) {
206 // Body with static shapes that is not kinematic, this is disallowed
207 qWarning() << "Cannot make body containing trimesh/heightfield/plane non-kinematic, "
208 "forcing kinematic.";
209 drb->setIsKinematic(true);
210 if (!frontendNode)
211 return;
212 }
213
214 const bool isKinematic = drb->isKinematic();
215 auto *dynamicBody = static_cast<physx::PxRigidDynamic *>(actor);
216
217 // Clear CCD before flipping kinematic mode: PhysX rejects sweep-based CCD on
218 // kinematic bodies, so leaving a stale CCD flag set while eKINEMATIC changes
219 // would trigger a spurious warning regardless of transition direction.
220 dynamicBody->setRigidBodyFlag(physx::PxRigidBodyFlag::eENABLE_CCD, false); // Sweep-based
221 dynamicBody->setRigidBodyFlag(physx::PxRigidBodyFlag::eENABLE_SPECULATIVE_CCD, false);
222
223 // Becoming kinematic has to be told before the shapes are built: PhysX
224 // refuses to attach a trimesh, heightfield or plane simulation shape to a
225 // body that is not kinematic yet, and drops it without telling the caller
226 // anything it checks.
227 if (isKinematic)
228 dynamicBody->setRigidBodyFlag(physx::PxRigidBodyFlag::eKINEMATIC, true);
229
230 buildShapes(physX);
231
232 // Ceasing to be kinematic has to wait until they have been, for the mirror
233 // reason: PhysX refuses to take eKINEMATIC off a body while such a shape is
234 // still attached, so the flag can only be dropped once buildShapes() has
235 // detached the shapes it objects to.
236 if (!isKinematic)
237 dynamicBody->setRigidBodyFlag(physx::PxRigidBodyFlag::eKINEMATIC, false);
238
239 // Density must be set after shapes so the inertia tensor is set
240 if (!drb->hasStaticShapes()) {
241 // Body with only dynamic shapes, set/calculate mass
242 QPhysicsCommand *command = nullptr;
243 switch (drb->massMode()) {
244 case QDynamicRigidBody::MassMode::DefaultDensity: {
245 command = new QPhysicsCommandSetDensity(world->defaultDensity());
246 break;
247 }
248 case QDynamicRigidBody::MassMode::CustomDensity: {
249 command = new QPhysicsCommandSetDensity(drb->density());
250 break;
251 }
252 case QDynamicRigidBody::MassMode::Mass: {
253 const float mass = qMax(drb->mass(), 0.f);
254 command = new QPhysicsCommandSetMass(mass);
255 break;
256 }
257 case QDynamicRigidBody::MassMode::MassAndInertiaTensor: {
258 const float mass = qMax(drb->mass(), 0.f);
259 command = new QPhysicsCommandSetMassAndInertiaTensor(mass, drb->inertiaTensor());
260 break;
261 }
262 case QDynamicRigidBody::MassMode::MassAndInertiaMatrix: {
263 const float mass = qMax(drb->mass(), 0.f);
264 command = new QPhysicsCommandSetMassAndInertiaMatrix(mass, drb->inertiaMatrix());
265 break;
266 }
267 }
268
269 drb->commandQueue().enqueue(command);
270 }
271
272 QDynamicRigidBody::CCDType ccd = drb->ccd();
273
274 switch (ccd) {
275 case QDynamicRigidBody::CCDType::SweepBasedCCD: {
276 // Kinematic bodies only support speculative CCD
277 dynamicBody->setRigidBodyFlag(physx::PxRigidBodyFlag::eENABLE_CCD, // Sweep-based
278 !isKinematic);
279 dynamicBody->setRigidBodyFlag(physx::PxRigidBodyFlag::eENABLE_SPECULATIVE_CCD, isKinematic);
280 break;
281 }
282
283 case QDynamicRigidBody::CCDType::SpeculativeCCD: {
284 dynamicBody->setRigidBodyFlag(physx::PxRigidBodyFlag::eENABLE_SPECULATIVE_CCD, true);
285 break;
286 }
287
288 case QDynamicRigidBody::CCDType::None: {
289 if (world->enableCCD()) {
290 // Kinematic bodies only support speculative CCD
291 dynamicBody->setRigidBodyFlag(physx::PxRigidBodyFlag::eENABLE_CCD, // Sweep-based
292 !isKinematic);
293 dynamicBody->setRigidBodyFlag(physx::PxRigidBodyFlag::eENABLE_SPECULATIVE_CCD,
294 isKinematic);
295 }
296 break;
297 }
298 }
299
300 setShapesDirty(false);
301}
302
304{
305 QDynamicRigidBody *rigidBody = static_cast<QDynamicRigidBody *>(frontendNode);
306 rigidBody->updateDefaultDensity(density);
307}
308
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)
PxTransformT< float > PxTransform
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::PxRigidDynamic &body)
#define QT_BEGIN_NAMESPACE
#define QT_END_NAMESPACE
static StaticPhysXObjects & getReference()