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 physx::PxTransform getPhysXLocalTransform(const QQuick3DNode *node)
28{
29 // Modify transforms to make the PhysX shapes match the QtQuick3D conventions
30 if (qobject_cast<const QPlaneShape *>(object: node) != nullptr) {
31 // Rotate the plane to make it match the built-in rectangle
32 const QQuaternion rotation = QPhysicsUtils::kMinus90YawRotation * node->rotation();
33 return physx::PxTransform(QPhysicsUtils::toPhysXType(qvec: node->position()),
34 QPhysicsUtils::toPhysXType(qquat: rotation));
35 } else if (auto *hf = qobject_cast<const QHeightFieldShape *>(object: node)) {
36 // Shift the height field so it's centered at the origin
37 return physx::PxTransform(QPhysicsUtils::toPhysXType(qvec: node->position() + hf->hfOffset()),
38 QPhysicsUtils::toPhysXType(qquat: node->rotation()));
39 }
40
41 const QQuaternion &rotation = node->rotation();
42 const QVector3D &localPosition = node->position();
43 const QVector3D &scale = node->sceneScale();
44 return physx::PxTransform(QPhysicsUtils::toPhysXType(qvec: localPosition * scale),
45 QPhysicsUtils::toPhysXType(qquat: rotation));
46}
47
48QPhysXActorBody::QPhysXActorBody(QAbstractPhysicsNode *frontEnd) : QAbstractPhysXNode(frontEnd) { }
49
50void QPhysXActorBody::cleanup(QPhysXWorld *physX)
51{
52 if (actor) {
53 physX->scene->removeActor(actor&: *actor);
54 PHYSX_RELEASE(actor);
55 }
56 QAbstractPhysXNode::cleanup(physX);
57}
58
59void QPhysXActorBody::init(QPhysicsWorld * /*world*/, QPhysXWorld *physX)
60{
61 Q_ASSERT(!actor);
62
63 createMaterial(physX);
64 createActor(physX);
65
66 actor->userData = reinterpret_cast<void *>(frontendNode);
67 physX->scene->addActor(actor&: *actor);
68 setShapesDirty(true);
69}
70
71void QPhysXActorBody::sync(float /*deltaTime*/,
72 QHash<QQuick3DNode *, QMatrix4x4> & /*transformCache*/)
73{
74 auto *body = static_cast<QAbstractPhysicsBody *>(frontendNode);
75 if (QPhysicsMaterial *qtMaterial = body->physicsMaterial()) {
76 const float staticFriction = qtMaterial->staticFriction();
77 const float dynamicFriction = qtMaterial->dynamicFriction();
78 const float restitution = qtMaterial->restitution();
79 if (material->getStaticFriction() != staticFriction)
80 material->setStaticFriction(staticFriction);
81 if (material->getDynamicFriction() != dynamicFriction)
82 material->setDynamicFriction(dynamicFriction);
83 if (material->getRestitution() != restitution)
84 material->setRestitution(restitution);
85 }
86}
87
88void QPhysXActorBody::markDirtyShapes()
89{
90 if (!frontendNode || !actor)
91 return;
92
93 // Go through the shapes and look for a change in pose (rotation, position)
94 // TODO: it is likely cheaper to connect a signal for changes on the position and rotation
95 // property and mark the node dirty then.
96 if (!shapesDirty()) {
97 const auto &collisionShapes = frontendNode->getCollisionShapesList();
98 const auto &physXShapes = shapes;
99
100 const int len = collisionShapes.size();
101 if (physXShapes.size() != len) {
102 // This should not really happen but check it anyway
103 setShapesDirty(true);
104 } else {
105 for (int i = 0; i < len; i++) {
106 auto poseNew = getPhysXLocalTransform(node: collisionShapes[i]);
107 auto poseOld = physXShapes[i]->getLocalPose();
108
109 if (!QPhysicsUtils::fuzzyEquals(a: poseNew, b: poseOld)) {
110 setShapesDirty(true);
111 break;
112 }
113 }
114 }
115 }
116}
117
118void QPhysXActorBody::rebuildDirtyShapes(QPhysicsWorld * /*world*/, QPhysXWorld *physX)
119{
120 if (!shapesDirty())
121 return;
122 buildShapes(physX);
123 setShapesDirty(false);
124}
125
126void QPhysXActorBody::createActor(QPhysXWorld * /*physX*/)
127{
128 auto &s_physx = StaticPhysXObjects::getReference();
129 const physx::PxTransform trf = QPhysicsUtils::toPhysXTransform(position: frontendNode->scenePosition(),
130 rotation: frontendNode->sceneRotation());
131 actor = s_physx.physics->createRigidDynamic(pose: trf);
132}
133
134bool QPhysXActorBody::debugGeometryCapability()
135{
136 return true;
137}
138
139physx::PxTransform QPhysXActorBody::getGlobalPose()
140{
141 return actor->getGlobalPose();
142}
143
144void QPhysXActorBody::buildShapes(QPhysXWorld * /*physX*/)
145{
146 auto body = actor;
147 for (auto *shape : shapes) {
148 body->detachShape(shape&: *shape);
149 PHYSX_RELEASE(shape);
150 }
151
152 // TODO: Only remove changed shapes?
153 shapes.clear();
154
155 for (const auto &collisionShape : frontendNode->getCollisionShapesList()) {
156 // TODO: shapes can be shared between multiple actors.
157 // Do we need to create new ones for every body?
158 auto *geom = collisionShape->getPhysXGeometry();
159 if (!geom || !material)
160 continue;
161
162 auto &s_physx = StaticPhysXObjects::getReference();
163 auto physXShape = s_physx.physics->createShape(geometry: *geom, material: *material);
164
165 if (useTriggerFlag()) {
166 physXShape->setFlag(flag: physx::PxShapeFlag::eSIMULATION_SHAPE, value: false);
167 physXShape->setFlag(flag: physx::PxShapeFlag::eTRIGGER_SHAPE, value: true);
168 }
169
170 { // Setup filtering
171 physx::PxFilterData filterData;
172 filterData.word0 = frontendNode->filterGroup();
173 filterData.word1 = frontendNode->filterIgnoreGroups();
174 physXShape->setSimulationFilterData(filterData);
175 }
176
177 shapes.push_back(t: physXShape);
178 physXShape->setLocalPose(getPhysXLocalTransform(node: collisionShape));
179 body->attachShape(shape&: *physXShape);
180 }
181
182 // Filters are always clean after building shapes
183 setFiltersDirty(false);
184}
185
186void QPhysXActorBody::updateFilters()
187{
188 if (!filtersDirty())
189 return;
190
191 // Go through all shapes and set the filter group and mask.
192 // TODO: What about shared shapes on several actors?
193 for (auto &physXShape : shapes) {
194 physx::PxFilterData filterData;
195 filterData.word0 = frontendNode->filterGroup();
196 filterData.word1 = frontendNode->filterIgnoreGroups();
197 physXShape->setSimulationFilterData(filterData);
198 }
199
200 setFiltersDirty(false);
201}
202
203QT_END_NAMESPACE
204

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