1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qphysxactorbody_p.h"
5
6#include "PxMaterial.h"
7#include "PxPhysics.h"
8#include "PxRigidDynamic.h"
9#include "PxRigidActor.h"
10#include "PxScene.h"
11
12#include "physxnode/qphysxworld_p.h"
13#include "qabstractphysicsbody_p.h"
14#include "qheightfieldshape_p.h"
15#include "qphysicsutils_p.h"
16#include "qplaneshape_p.h"
17#include "qstaticphysxobjects_p.h"
18
19#define PHYSX_RELEASE(x) \
20 if (x != nullptr) { \
21 x->release(); \
22 x = nullptr; \
23 }
24
25QT_BEGIN_NAMESPACE
26
27static const QQuaternion kMinus90YawRotation = QQuaternion::fromEulerAngles(pitch: 0, yaw: -90, roll: 0);
28
29static inline bool fuzzyEquals(const physx::PxTransform &a, const physx::PxTransform &b)
30{
31 return qFuzzyCompare(p1: a.p.x, p2: b.p.x) && qFuzzyCompare(p1: a.p.y, p2: b.p.y) && qFuzzyCompare(p1: a.p.z, p2: b.p.z)
32 && qFuzzyCompare(p1: a.q.x, p2: b.q.x) && qFuzzyCompare(p1: a.q.y, p2: b.q.y)
33 && qFuzzyCompare(p1: a.q.z, p2: b.q.z) && qFuzzyCompare(p1: a.q.w, p2: b.q.w);
34}
35
36static physx::PxTransform getPhysXLocalTransform(const QQuick3DNode *node)
37{
38 // Modify transforms to make the PhysX shapes match the QtQuick3D conventions
39 if (qobject_cast<const QPlaneShape *>(object: node) != nullptr) {
40 // Rotate the plane to make it match the built-in rectangle
41 const QQuaternion rotation = kMinus90YawRotation * node->rotation();
42 return physx::PxTransform(QPhysicsUtils::toPhysXType(qvec: node->position()),
43 QPhysicsUtils::toPhysXType(qquat: rotation));
44 } else if (auto *hf = qobject_cast<const QHeightFieldShape *>(object: node)) {
45 // Shift the height field so it's centered at the origin
46 return physx::PxTransform(QPhysicsUtils::toPhysXType(qvec: node->position() + hf->hfOffset()),
47 QPhysicsUtils::toPhysXType(qquat: node->rotation()));
48 }
49
50 const QQuaternion &rotation = node->rotation();
51 const QVector3D &localPosition = node->position();
52 const QVector3D &scale = node->sceneScale();
53 return physx::PxTransform(QPhysicsUtils::toPhysXType(qvec: localPosition * scale),
54 QPhysicsUtils::toPhysXType(qquat: rotation));
55}
56
57QPhysXActorBody::QPhysXActorBody(QAbstractPhysicsNode *frontEnd) : QAbstractPhysXNode(frontEnd) { }
58
59void QPhysXActorBody::cleanup(QPhysXWorld *physX)
60{
61 if (actor) {
62 physX->scene->removeActor(actor&: *actor);
63 PHYSX_RELEASE(actor);
64 }
65 QAbstractPhysXNode::cleanup(physX);
66}
67
68void QPhysXActorBody::init(QPhysicsWorld * /*world*/, QPhysXWorld *physX)
69{
70 Q_ASSERT(!actor);
71
72 createMaterial(physX);
73 createActor(physX);
74
75 actor->userData = reinterpret_cast<void *>(frontendNode);
76 physX->scene->addActor(actor&: *actor);
77 setShapesDirty(true);
78}
79
80void QPhysXActorBody::sync(float /*deltaTime*/,
81 QHash<QQuick3DNode *, QMatrix4x4> & /*transformCache*/)
82{
83 auto *body = static_cast<QAbstractPhysicsBody *>(frontendNode);
84 if (QPhysicsMaterial *qtMaterial = body->physicsMaterial()) {
85 const float staticFriction = qtMaterial->staticFriction();
86 const float dynamicFriction = qtMaterial->dynamicFriction();
87 const float restitution = qtMaterial->restitution();
88 if (material->getStaticFriction() != staticFriction)
89 material->setStaticFriction(staticFriction);
90 if (material->getDynamicFriction() != dynamicFriction)
91 material->setDynamicFriction(dynamicFriction);
92 if (material->getRestitution() != restitution)
93 material->setRestitution(restitution);
94 }
95}
96
97void QPhysXActorBody::markDirtyShapes()
98{
99 if (!frontendNode || !actor)
100 return;
101
102 // Go through the shapes and look for a change in pose (rotation, position)
103 // TODO: it is likely cheaper to connect a signal for changes on the position and rotation
104 // property and mark the node dirty then.
105 if (!shapesDirty()) {
106 const auto &collisionShapes = frontendNode->getCollisionShapesList();
107 const auto &physXShapes = shapes;
108
109 const int len = collisionShapes.size();
110 if (physXShapes.size() != len) {
111 // This should not really happen but check it anyway
112 setShapesDirty(true);
113 } else {
114 for (int i = 0; i < len; i++) {
115 auto poseNew = getPhysXLocalTransform(node: collisionShapes[i]);
116 auto poseOld = physXShapes[i]->getLocalPose();
117
118 if (!fuzzyEquals(a: poseNew, b: poseOld)) {
119 setShapesDirty(true);
120 break;
121 }
122 }
123 }
124 }
125}
126
127void QPhysXActorBody::rebuildDirtyShapes(QPhysicsWorld * /*world*/, QPhysXWorld *physX)
128{
129 if (!shapesDirty())
130 return;
131 buildShapes(physX);
132 setShapesDirty(false);
133}
134
135void QPhysXActorBody::createActor(QPhysXWorld * /*physX*/)
136{
137 auto &s_physx = StaticPhysXObjects::getReference();
138 const physx::PxTransform trf = QPhysicsUtils::toPhysXTransform(position: frontendNode->scenePosition(),
139 rotation: frontendNode->sceneRotation());
140 actor = s_physx.physics->createRigidDynamic(pose: trf);
141}
142
143bool QPhysXActorBody::debugGeometryCapability()
144{
145 return true;
146}
147
148physx::PxTransform QPhysXActorBody::getGlobalPose()
149{
150 return actor->getGlobalPose();
151}
152
153void QPhysXActorBody::buildShapes(QPhysXWorld * /*physX*/)
154{
155 auto body = actor;
156 for (auto *shape : shapes) {
157 body->detachShape(shape&: *shape);
158 PHYSX_RELEASE(shape);
159 }
160
161 // TODO: Only remove changed shapes?
162 shapes.clear();
163
164 for (const auto &collisionShape : frontendNode->getCollisionShapesList()) {
165 // TODO: shapes can be shared between multiple actors.
166 // Do we need to create new ones for every body?
167 auto *geom = collisionShape->getPhysXGeometry();
168 if (!geom || !material)
169 continue;
170
171 auto &s_physx = StaticPhysXObjects::getReference();
172 auto physXShape = s_physx.physics->createShape(geometry: *geom, material: *material);
173
174 if (useTriggerFlag()) {
175 physXShape->setFlag(flag: physx::PxShapeFlag::eSIMULATION_SHAPE, value: false);
176 physXShape->setFlag(flag: physx::PxShapeFlag::eTRIGGER_SHAPE, value: true);
177 }
178
179 shapes.push_back(t: physXShape);
180 physXShape->setLocalPose(getPhysXLocalTransform(node: collisionShape));
181 body->attachShape(shape&: *physXShape);
182 }
183}
184
185QT_END_NAMESPACE
186

source code of qtquick3dphysics/src/quick3dphysics/physxnode/qphysxactorbody.cpp