7#include "physxnode/qabstractphysxnode_p.h"
8#include "physxnode/qphysxworld_p.h"
12#include "joints/qjoint_p.h"
23#include "PxPhysicsAPI.h"
24#include "cooking/PxCooking.h"
25#include <foundation/PxSimpleTypes.h>
27#include <QtQuick/private/qquickframeanimation_p.h>
28#include <QtQuick3D/private/qquick3dobject_p.h>
29#include <QtQuick3D/private/qquick3dnode_p.h>
30#include <QtQuick3D/private/qquick3dmodel_p.h>
31#include <QtQuick3D/private/qquick3dprincipledmaterial_p.h>
32#include <QtQuick3DUtils/private/qssgutils_p.h>
34#include <QtGui/qquaternion.h>
36#include <QtEnvironmentVariables>
38#define PHYSX_ENABLE_PVD 0
43
44
45
46
47
48
49
50
51
54
55
56
57
58
61
62
63
66
67
68
69
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
97
98
99
100
101
102
103
106
107
108
109
110
111
112
113
114
115
118
119
120
121
122
123
124
125
126
127
130
131
132
133
134
135
138
139
140
141
142
143
144
145
146
147
148
151
152
153
154
155
156
157
158
159
160
161
164
165
166
167
168
169
170
171
174
175
176
177
178
179
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
214
215
216
217
218
219
220
221
222
223
224
225
226
229
230
231
232
233
234
235
236
237
238
239
240
241
242
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
329void QPhysicsWorld::DebugModelHolder::releaseMeshPointer()
331 if (
auto base =
static_cast<physx::PxBase *>(ptr); base)
336const QVector3D &QPhysicsWorld::DebugModelHolder::halfExtents()
const
340void QPhysicsWorld::DebugModelHolder::setHalfExtents(
const QVector3D &halfExtents)
344float QPhysicsWorld::DebugModelHolder::radius()
const
348void QPhysicsWorld::DebugModelHolder::setRadius(
float radius)
352float QPhysicsWorld::DebugModelHolder::heightScale()
const
356void QPhysicsWorld::DebugModelHolder::setHeightScale(
float heightScale)
358 data.setX(heightScale);
360float QPhysicsWorld::DebugModelHolder::halfHeight()
const
364void QPhysicsWorld::DebugModelHolder::setHalfHeight(
float halfHeight)
366 data.setY(halfHeight);
368float QPhysicsWorld::DebugModelHolder::rowScale()
const
372void QPhysicsWorld::DebugModelHolder::setRowScale(
float rowScale)
376float QPhysicsWorld::DebugModelHolder::columnScale()
const
380void QPhysicsWorld::DebugModelHolder::setColumnScale(
float columnScale)
382 data.setZ(columnScale);
384physx::PxConvexMesh *QPhysicsWorld::DebugModelHolder::getConvexMesh()
386 return static_cast<physx::PxConvexMesh *>(ptr);
388void QPhysicsWorld::DebugModelHolder::setConvexMesh(physx::PxConvexMesh *mesh)
390 ptr =
static_cast<
void *>(mesh);
392physx::PxTriangleMesh *QPhysicsWorld::DebugModelHolder::getTriangleMesh()
394 return static_cast<physx::PxTriangleMesh *>(ptr);
396void QPhysicsWorld::DebugModelHolder::setTriangleMesh(physx::PxTriangleMesh *mesh)
398 ptr =
static_cast<
void *>(mesh);
400physx::PxHeightField *QPhysicsWorld::DebugModelHolder::getHeightField()
402 return static_cast<physx::PxHeightField *>(ptr);
404void QPhysicsWorld::DebugModelHolder::setHeightField(physx::PxHeightField *hf)
406 ptr =
static_cast<physx::PxHeightField *>(hf);
420void QPhysicsWorld::registerNode(QAbstractPhysicsNode *physicsNode)
422 auto world = getWorld(physicsNode);
424 world->m_newPhysicsNodes.push_back(physicsNode);
426 worldManager.orphanNodes.push_back(physicsNode);
430void QPhysicsWorld::deregisterNode(QAbstractPhysicsNode *physicsNode)
432 for (
auto world : std::as_const(worldManager.worlds)) {
433 world->m_newPhysicsNodes.removeAll(physicsNode);
434 QMutexLocker locker(&world->m_removedPhysicsNodesMutex);
435 if (physicsNode->m_backendObject) {
436 Q_ASSERT(physicsNode->m_backendObject->frontendNode == physicsNode);
437 physicsNode->m_backendObject->frontendNode =
nullptr;
438 physicsNode->m_backendObject->isRemoved =
true;
439 physicsNode->m_backendObject =
nullptr;
441 world->m_removedPhysicsNodes.insert(physicsNode);
443 worldManager.orphanNodes.removeAll(physicsNode);
446void QPhysicsWorld::registerJoint(QPhysicsJoint *joint)
448 auto world = getWorld(joint);
450 world->m_joints.push_back(joint);
452 worldManager.orphanJoints.push_back(joint);
456void QPhysicsWorld::deregisterJoint(QPhysicsJoint *joint)
458 for (
auto world : worldManager.worlds) {
459 QMutexLocker locker(&world->m_removedPhysicsNodesMutex);
460 world->m_removedJoints.insert(joint->getPhysXBackend());
463 qsizetype idx = world->m_joints.indexOf(joint);
465 world->m_joints.swapItemsAt(idx, world->m_joints.size() - 1);
466 world->m_joints.pop_back();
469 worldManager.orphanJoints.removeAll(joint);
472void QPhysicsWorld::registerContact(QAbstractPhysicsNode *sender, QAbstractPhysicsNode *receiver,
473 const QVector<QVector3D> &positions,
474 const QVector<QVector3D> &impulses,
475 const QVector<QVector3D> &normals)
484 contact.sender = sender;
485 contact.receiver = receiver;
486 contact.positions = positions;
487 contact.impulses = impulses;
488 contact.normals = normals;
490 m_registeredContacts.push_back(contact);
493QPhysicsWorld::QPhysicsWorld(QObject *parent) : QObject(parent)
495 m_inDesignStudio = !qEnvironmentVariableIsEmpty(
"QML_PUPPET_MODE");
496 m_physx =
new QPhysXWorld;
497 m_physx->createWorld();
499 worldManager.worlds.push_back(
this);
503 m_frameAnimator =
new FrameAnimator;
504 connect(m_frameAnimator, &QQuickFrameAnimation::triggered,
this,
505 &QPhysicsWorld::simulateFrame);
508QPhysicsWorld::~QPhysicsWorld()
510 if (m_frameAnimator) {
511 m_frameAnimator->stop();
512 delete m_frameAnimator;
515 if (m_physx->scene && m_physx->isRunning && !m_frameFetched)
516 m_physx->scene->fetchResults(
true);
518 for (
auto body : std::as_const(m_physXBodies)) {
519 body->cleanup(m_physx);
522 m_physXBodies.clear();
523 m_physx->deleteWorld();
525 worldManager.worlds.removeAll(
this);
527 if (!qtPhysicsTimingsFile.isEmpty()) {
528 if (m_frameTimings.isEmpty()) {
529 qWarning() <<
"No frame timings saved.";
530 }
else if (
auto csvFile = QFile(qtPhysicsTimingsFile); csvFile.open(QIODevice::WriteOnly)) {
531 QTextStream out(&csvFile);
532 for (
int i = 1; i < m_frameTimings.size(); i++) {
533 out << i <<
"," << m_frameTimings[i] <<
'\n';
537 qWarning() <<
"Could not open timings file " << qtPhysicsTimingsFile;
542void QPhysicsWorld::classBegin() {}
544void QPhysicsWorld::componentComplete()
546 if ((!m_running && !m_inDesignStudio) || m_physicsInitialized)
551QVector3D QPhysicsWorld::gravity()
const
556bool QPhysicsWorld::running()
const
561bool QPhysicsWorld::forceDebugDraw()
const
563 return m_forceDebugDraw;
566bool QPhysicsWorld::enableCCD()
const
571float QPhysicsWorld::typicalLength()
const
573 return m_typicalLength;
576float QPhysicsWorld::typicalSpeed()
const
578 return m_typicalSpeed;
581bool QPhysicsWorld::isNodeRemoved(QAbstractPhysicsNode *object)
583 return m_removedPhysicsNodes.contains(object);
586void QPhysicsWorld::setGravity(QVector3D gravity)
588 if (m_gravity == gravity)
591 if (!QPhysicsUtils::isFinite(gravity)) {
592 qWarning() <<
"Warning: 'gravity' must be finite, ignored";
597 if (m_physx->scene) {
598 m_physx->scene->setGravity(QPhysicsUtils::toPhysXType(m_gravity));
600 emit gravityChanged(m_gravity);
603void QPhysicsWorld::setRunning(
bool running)
605 if (m_running == running)
609 if (!m_inDesignStudio && m_running && !m_physicsInitialized)
613 m_frameAnimator->start();
615 m_frameAnimator->stop();
617 emit runningChanged(m_running);
620void QPhysicsWorld::setForceDebugDraw(
bool forceDebugDraw)
622 if (m_forceDebugDraw == forceDebugDraw)
625 m_forceDebugDraw = forceDebugDraw;
626 if (!m_forceDebugDraw)
630 emit forceDebugDrawChanged(m_forceDebugDraw);
633QQuick3DNode *QPhysicsWorld::viewport()
const
638void QPhysicsWorld::setHasIndividualDebugDraw()
640 m_hasIndividualDebugDraw =
true;
643void QPhysicsWorld::setViewport(QQuick3DNode *viewport)
645 if (m_viewport == viewport)
648 m_viewport = viewport;
651 for (
auto material : std::as_const(m_debugMaterials))
653 m_debugMaterials.clear();
655 for (
auto &holder : m_collisionShapeDebugModels) {
656 holder.releaseMeshPointer();
659 m_collisionShapeDebugModels.clear();
661 emit viewportChanged(m_viewport);
664void QPhysicsWorld::setMinimumTimestep(
float minTimestep)
666 if (qFuzzyCompare(m_minTimestep, minTimestep))
669 if (!qIsFinite(minTimestep)) {
670 qWarning(
"Minimum timestep must be finite, ignoring");
674 if (minTimestep > m_maxTimestep) {
675 qWarning(
"Minimum timestep greater than maximum timestep, value clamped");
676 minTimestep = qMin(minTimestep, m_maxTimestep);
679 if (minTimestep < 0.f) {
680 qWarning(
"Minimum timestep less than zero, value clamped");
681 minTimestep = qMax(minTimestep, 0.f);
684 if (qFuzzyCompare(m_minTimestep, minTimestep))
687 m_minTimestep = minTimestep;
688 emit minimumTimestepChanged(m_minTimestep);
691void QPhysicsWorld::setMaximumTimestep(
float maxTimestep)
693 if (qFuzzyCompare(m_maxTimestep, maxTimestep))
696 if (!qIsFinite(maxTimestep)) {
697 qWarning(
"Maximum timestep must be finite, ignoring");
701 if (maxTimestep < 0.f) {
702 qWarning(
"Maximum timestep less than zero, value clamped");
703 maxTimestep = qMax(maxTimestep, 0.f);
706 if (qFuzzyCompare(m_maxTimestep, maxTimestep))
709 m_maxTimestep = maxTimestep;
710 emit maximumTimestepChanged(maxTimestep);
713void QPhysicsWorld::setupDebugMaterials(QQuick3DNode *sceneNode)
715 if (!m_debugMaterials.isEmpty())
718 const int lineWidth = m_inDesignStudio ? 1 : 3;
721 for (
auto color : { QColorConstants::Svg::chartreuse, QColorConstants::Svg::cyan,
722 QColorConstants::Svg::lightsalmon, QColorConstants::Svg::red,
723 QColorConstants::Svg::blueviolet, QColorConstants::Svg::black }) {
724 auto debugMaterial =
new QQuick3DPrincipledMaterial();
725 debugMaterial->setLineWidth(lineWidth);
726 debugMaterial->setParentItem(sceneNode);
727 debugMaterial->setParent(sceneNode);
728 debugMaterial->setBaseColor(color);
729 debugMaterial->setLighting(QQuick3DPrincipledMaterial::NoLighting);
730 debugMaterial->setCullMode(QQuick3DMaterial::NoCulling);
731 m_debugMaterials.push_back(debugMaterial);
735void QPhysicsWorld::updateDebugDraw()
737 if (!(m_forceDebugDraw || m_hasIndividualDebugDraw)) {
739 for (
auto &holder : m_collisionShapeDebugModels) {
740 holder.releaseMeshPointer();
743 m_collisionShapeDebugModels.clear();
748 auto sceneNode = m_viewport ? m_viewport : m_scene;
750 if (sceneNode ==
nullptr)
753 setupDebugMaterials(sceneNode);
754 m_hasIndividualDebugDraw =
false;
757 QSet<QPair<QAbstractCollisionShape *, QAbstractPhysXNode *>> currentCollisionShapes;
758 currentCollisionShapes.reserve(m_collisionShapeDebugModels.size());
760 for (QAbstractPhysXNode *node : std::as_const(m_physXBodies)) {
761 if (!node->debugGeometryCapability())
764 const auto &collisionShapes = node->frontendNode->getCollisionShapesList();
765 const int materialIdx =
static_cast<
int>(node->getDebugDrawBodyType());
766 const int length = collisionShapes.length();
767 for (
int idx = 0; idx < length; idx++) {
768 const auto collisionShape = collisionShapes[idx];
770 if (!m_forceDebugDraw && !collisionShape->enableDebugDraw())
773 DebugModelHolder &holder =
774 m_collisionShapeDebugModels[std::make_pair(collisionShape, node)];
775 auto &model = holder.model;
777 currentCollisionShapes.insert(std::make_pair(collisionShape, node));
779 m_hasIndividualDebugDraw =
780 m_hasIndividualDebugDraw || collisionShape->enableDebugDraw();
784 model =
new QQuick3DModel();
785 model->setParentItem(sceneNode);
786 model->setParent(sceneNode);
787 model->setCastsShadows(
false);
788 model->setReceivesShadows(
false);
789 model->setCastsReflections(
false);
792 model->setVisible(
true);
795 auto material = m_debugMaterials[materialIdx];
796 QQmlListReference materialsRef(model,
"materials");
797 if (materialsRef.count() == 0 || materialsRef.at(0) != material) {
798 materialsRef.clear();
799 materialsRef.append(material);
805 if (qobject_cast<QCharacterController *>(node->frontendNode)) {
806 QCapsuleShape *capsuleShape = qobject_cast<QCapsuleShape *>(collisionShape);
810 const float radius = capsuleShape->diameter() * 0.5;
811 const float halfHeight = capsuleShape->height() * 0.5;
813 if (!qFuzzyCompare(radius, holder.radius())
814 || !qFuzzyCompare(halfHeight, holder.halfHeight())) {
815 auto geom = QDebugDrawHelper::generateCapsuleGeometry(radius, halfHeight);
816 geom->setParent(model);
817 model->setGeometry(geom);
818 holder.setRadius(radius);
819 holder.setHalfHeight(halfHeight);
822 model->setPosition(node->frontendNode->scenePosition());
823 model->setRotation(node->frontendNode->sceneRotation()
824 * QQuaternion::fromEulerAngles(0, 0, 90));
828 if (node->shapes.length() < length)
831 const auto physXShape = node->shapes[idx];
832 auto localPose = physXShape->getLocalPose();
834 switch (physXShape->getGeometryType()) {
835 case physx::PxGeometryType::eBOX: {
836 physx::PxBoxGeometry boxGeometry;
837 physXShape->getBoxGeometry(boxGeometry);
838 const auto &halfExtentsOld = holder.halfExtents();
839 const auto halfExtents = QPhysicsUtils::toQtType(boxGeometry.halfExtents);
840 if (!qFuzzyCompare(halfExtentsOld, halfExtents)) {
841 auto geom = QDebugDrawHelper::generateBoxGeometry(halfExtents);
842 geom->setParent(model);
843 model->setGeometry(geom);
844 holder.setHalfExtents(halfExtents);
850 case physx::PxGeometryType::eSPHERE: {
851 physx::PxSphereGeometry sphereGeometry;
852 physXShape->getSphereGeometry(sphereGeometry);
853 const float radius = holder.radius();
854 if (!qFuzzyCompare(sphereGeometry.radius, radius)) {
855 auto geom = QDebugDrawHelper::generateSphereGeometry(sphereGeometry.radius);
856 geom->setParent(model);
857 model->setGeometry(geom);
858 holder.setRadius(sphereGeometry.radius);
863 case physx::PxGeometryType::eCAPSULE: {
864 physx::PxCapsuleGeometry capsuleGeometry;
865 physXShape->getCapsuleGeometry(capsuleGeometry);
866 const float radius = holder.radius();
867 const float halfHeight = holder.halfHeight();
869 if (!qFuzzyCompare(capsuleGeometry.radius, radius)
870 || !qFuzzyCompare(capsuleGeometry.halfHeight, halfHeight)) {
871 auto geom = QDebugDrawHelper::generateCapsuleGeometry(
872 capsuleGeometry.radius, capsuleGeometry.halfHeight);
873 geom->setParent(model);
874 model->setGeometry(geom);
875 holder.setRadius(capsuleGeometry.radius);
876 holder.setHalfHeight(capsuleGeometry.halfHeight);
881 case physx::PxGeometryType::ePLANE:{
882 physx::PxPlaneGeometry planeGeometry;
883 physXShape->getPlaneGeometry(planeGeometry);
885 const QQuaternion rotation =
886 QPhysicsUtils::kMinus90YawRotation * QPhysicsUtils::toQtType(localPose.q);
887 localPose = physx::PxTransform(localPose.p, QPhysicsUtils::toPhysXType(rotation));
889 if (model->geometry() ==
nullptr) {
890 auto geom = QDebugDrawHelper::generatePlaneGeometry();
891 geom->setParent(model);
892 model->setGeometry(geom);
900 case physx::PxGeometryType::eHEIGHTFIELD: {
901 physx::PxHeightFieldGeometry heightFieldGeometry;
902 bool success = physXShape->getHeightFieldGeometry(heightFieldGeometry);
904 const float heightScale = holder.heightScale();
905 const float rowScale = holder.rowScale();
906 const float columnScale = holder.columnScale();
908 if (
auto heightField = holder.getHeightField();
909 heightField && heightField != heightFieldGeometry.heightField) {
910 heightField->release();
911 holder.setHeightField(
nullptr);
914 if (!qFuzzyCompare(heightFieldGeometry.heightScale, heightScale)
915 || !qFuzzyCompare(heightFieldGeometry.rowScale, rowScale)
916 || !qFuzzyCompare(heightFieldGeometry.columnScale, columnScale)
917 || !holder.getHeightField()) {
918 if (!holder.getHeightField()) {
919 heightFieldGeometry.heightField->acquireReference();
920 holder.setHeightField(heightFieldGeometry.heightField);
922 auto geom = QDebugDrawHelper::generateHeightFieldGeometry(
923 heightFieldGeometry.heightField, heightFieldGeometry.heightScale,
924 heightFieldGeometry.rowScale, heightFieldGeometry.columnScale);
925 geom->setParent(model);
926 model->setGeometry(geom);
927 holder.setHeightScale(heightFieldGeometry.heightScale);
928 holder.setRowScale(heightFieldGeometry.rowScale);
929 holder.setColumnScale(heightFieldGeometry.columnScale);
934 case physx::PxGeometryType::eCONVEXMESH: {
935 physx::PxConvexMeshGeometry convexMeshGeometry;
936 const bool success = physXShape->getConvexMeshGeometry(convexMeshGeometry);
938 const auto rotation = convexMeshGeometry.scale.rotation * localPose.q;
939 localPose = physx::PxTransform(localPose.p, rotation);
940 model->setScale(QPhysicsUtils::toQtType(convexMeshGeometry.scale.scale));
942 if (
auto convexMesh = holder.getConvexMesh();
943 convexMesh && convexMesh != convexMeshGeometry.convexMesh) {
944 convexMesh->release();
945 holder.setConvexMesh(
nullptr);
948 if (!model->geometry() || !holder.getConvexMesh()) {
949 if (!holder.getConvexMesh()) {
950 convexMeshGeometry.convexMesh->acquireReference();
951 holder.setConvexMesh(convexMeshGeometry.convexMesh);
953 auto geom = QDebugDrawHelper::generateConvexMeshGeometry(
954 convexMeshGeometry.convexMesh);
955 geom->setParent(model);
956 model->setGeometry(geom);
961 case physx::PxGeometryType::eTRIANGLEMESH: {
962 physx::PxTriangleMeshGeometry triangleMeshGeometry;
963 const bool success = physXShape->getTriangleMeshGeometry(triangleMeshGeometry);
965 const auto rotation = triangleMeshGeometry.scale.rotation * localPose.q;
966 localPose = physx::PxTransform(localPose.p, rotation);
967 model->setScale(QPhysicsUtils::toQtType(triangleMeshGeometry.scale.scale));
969 if (
auto triangleMesh = holder.getTriangleMesh();
970 triangleMesh && triangleMesh != triangleMeshGeometry.triangleMesh) {
971 triangleMesh->release();
972 holder.setTriangleMesh(
nullptr);
975 if (!model->geometry() || !holder.getTriangleMesh()) {
976 if (!holder.getTriangleMesh()) {
977 triangleMeshGeometry.triangleMesh->acquireReference();
978 holder.setTriangleMesh(triangleMeshGeometry.triangleMesh);
980 auto geom = QDebugDrawHelper::generateTriangleMeshGeometry(
981 triangleMeshGeometry.triangleMesh);
982 geom->setParent(model);
983 model->setGeometry(geom);
988 case physx::PxGeometryType::eINVALID:
989 case physx::PxGeometryType::eGEOMETRY_COUNT:
994 auto globalPose = node->getGlobalPose();
995 auto finalPose = globalPose.transform(localPose);
997 model->setRotation(QPhysicsUtils::toQtType(finalPose.q));
998 model->setPosition(QPhysicsUtils::toQtType(finalPose.p));
1003 m_collisionShapeDebugModels.removeIf(
1004 [&](QHash<QPair<QAbstractCollisionShape *, QAbstractPhysXNode *>,
1005 DebugModelHolder>::iterator it) {
1006 if (!currentCollisionShapes.contains(it.key())) {
1007 auto holder = it.value();
1008 holder.releaseMeshPointer();
1010 delete holder.model;
1019 if (
auto shape = qobject_cast<QAbstractPhysicsNode *>(node)) {
1020 nodes.push_back(shape);
1024 auto childItems = node->childItems();
1025 for (QQuick3DObject *child : std::as_const(childItems))
1026 collectPhysicsNodes(child, nodes);
1029void QPhysicsWorld::updateDebugDrawDesignStudio()
1032 auto sceneNode = m_viewport ? m_viewport : m_scene;
1034 if (sceneNode ==
nullptr)
1037 setupDebugMaterials(sceneNode);
1040 QSet<QPair<QAbstractCollisionShape *, QAbstractPhysicsNode *>> currentCollisionShapes;
1041 currentCollisionShapes.reserve(m_collisionShapeDebugModels.size());
1043 QList<QAbstractPhysicsNode *> activePhysicsNodes;
1044 activePhysicsNodes.reserve(m_collisionShapeDebugModels.size());
1045 collectPhysicsNodes(m_scene, activePhysicsNodes);
1047 for (QAbstractPhysicsNode *node : std::as_const(activePhysicsNodes)) {
1049 const auto &collisionShapes = node->getCollisionShapesList();
1050 const int materialIdx = 0;
1051 const int length = collisionShapes.length();
1053 const bool isCharacterController = qobject_cast<QCharacterController *>(node) !=
nullptr;
1055 for (
int idx = 0; idx < length; idx++) {
1056 QAbstractCollisionShape *collisionShape = collisionShapes[idx];
1057 DebugModelHolder &holder =
1058 m_DesignStudioDebugModels[std::make_pair(collisionShape, node)];
1059 auto &model = holder.model;
1061 currentCollisionShapes.insert(std::make_pair(collisionShape, node));
1063 m_hasIndividualDebugDraw =
1064 m_hasIndividualDebugDraw || collisionShape->enableDebugDraw();
1071 model =
new QQuick3DModel();
1072 model->setParentItem(sceneNode);
1073 model->setParent(sceneNode);
1074 model->setCastsShadows(
false);
1075 model->setReceivesShadows(
false);
1076 model->setCastsReflections(
false);
1079 const bool hasGeometry = holder.geometry !=
nullptr;
1080 QVector3D scenePosition = collisionShape->scenePosition();
1081 QQuaternion sceneRotation = collisionShape->sceneRotation();
1082 QQuick3DGeometry *newGeometry =
nullptr;
1084 if (isCharacterController)
1085 sceneRotation = sceneRotation * QQuaternion::fromEulerAngles(QVector3D(0, 0, 90));
1088 auto material = m_debugMaterials[materialIdx];
1089 QQmlListReference materialsRef(model,
"materials");
1090 if (materialsRef.count() == 0 || materialsRef.at(0) != material) {
1091 materialsRef.clear();
1092 materialsRef.append(material);
1096 if (
auto shape = qobject_cast<QBoxShape *>(collisionShape)) {
1097 const auto &halfExtentsOld = holder.halfExtents();
1098 const auto halfExtents = shape->sceneScale() * shape->extents() * 0.5f;
1099 if (!qFuzzyCompare(halfExtentsOld, halfExtents) || !hasGeometry) {
1100 newGeometry = QDebugDrawHelper::generateBoxGeometry(halfExtents);
1101 holder.setHalfExtents(halfExtents);
1103 }
else if (
auto shape = qobject_cast<QSphereShape *>(collisionShape)) {
1104 const float radiusOld = holder.radius();
1105 const float radius = shape->sceneScale().x() * shape->diameter() * 0.5f;
1106 if (!qFuzzyCompare(radiusOld, radius) || !hasGeometry) {
1107 newGeometry = QDebugDrawHelper::generateSphereGeometry(radius);
1108 holder.setRadius(radius);
1110 }
else if (
auto shape = qobject_cast<QCapsuleShape *>(collisionShape)) {
1111 const float radiusOld = holder.radius();
1112 const float halfHeightOld = holder.halfHeight();
1113 const float radius = shape->sceneScale().y() * shape->diameter() * 0.5f;
1114 const float halfHeight = shape->sceneScale().x() * shape->height() * 0.5f;
1116 if ((!qFuzzyCompare(radiusOld, radius) || !qFuzzyCompare(halfHeightOld, halfHeight))
1118 newGeometry = QDebugDrawHelper::generateCapsuleGeometry(radius, halfHeight);
1119 holder.setRadius(radius);
1120 holder.setHalfHeight(halfHeight);
1122 }
else if (qobject_cast<QPlaneShape *>(collisionShape)) {
1124 newGeometry = QDebugDrawHelper::generatePlaneGeometry();
1125 }
else if (
auto shape = qobject_cast<QHeightFieldShape *>(collisionShape)) {
1126 physx::PxHeightFieldGeometry *heightFieldGeometry =
1127 static_cast<physx::PxHeightFieldGeometry *>(shape->getPhysXGeometry());
1128 const float heightScale = holder.heightScale();
1129 const float rowScale = holder.rowScale();
1130 const float columnScale = holder.columnScale();
1131 scenePosition += shape->hfOffset();
1132 if (!heightFieldGeometry) {
1133 qWarning() <<
"Could not get height field";
1134 }
else if (!qFuzzyCompare(heightFieldGeometry->heightScale, heightScale)
1135 || !qFuzzyCompare(heightFieldGeometry->rowScale, rowScale)
1136 || !qFuzzyCompare(heightFieldGeometry->columnScale, columnScale)
1138 newGeometry = QDebugDrawHelper::generateHeightFieldGeometry(
1139 heightFieldGeometry->heightField, heightFieldGeometry->heightScale,
1140 heightFieldGeometry->rowScale, heightFieldGeometry->columnScale);
1141 holder.setHeightScale(heightFieldGeometry->heightScale);
1142 holder.setRowScale(heightFieldGeometry->rowScale);
1143 holder.setColumnScale(heightFieldGeometry->columnScale);
1145 }
else if (
auto shape = qobject_cast<QConvexMeshShape *>(collisionShape)) {
1146 auto convexMeshGeometry =
1147 static_cast<physx::PxConvexMeshGeometry *>(shape->getPhysXGeometry());
1148 if (!convexMeshGeometry) {
1149 qWarning() <<
"Could not get convex mesh";
1151 model->setScale(QPhysicsUtils::toQtType(convexMeshGeometry->scale.scale));
1154 newGeometry = QDebugDrawHelper::generateConvexMeshGeometry(
1155 convexMeshGeometry->convexMesh);
1158 }
else if (
auto shape = qobject_cast<QTriangleMeshShape *>(collisionShape)) {
1159 physx::PxTriangleMeshGeometry *triangleMeshGeometry =
1160 static_cast<physx::PxTriangleMeshGeometry *>(shape->getPhysXGeometry());
1161 if (!triangleMeshGeometry) {
1162 qWarning() <<
"Could not get triangle mesh";
1164 model->setScale(QPhysicsUtils::toQtType(triangleMeshGeometry->scale.scale));
1167 newGeometry = QDebugDrawHelper::generateTriangleMeshGeometry(
1168 triangleMeshGeometry->triangleMesh);
1174 delete holder.geometry;
1175 holder.geometry = newGeometry;
1178 model->setGeometry(holder.geometry);
1179 model->setVisible(
true);
1181 model->setRotation(sceneRotation);
1182 model->setPosition(scenePosition);
1187 m_DesignStudioDebugModels.removeIf(
1188 [&](QHash<QPair<QAbstractCollisionShape *, QAbstractPhysicsNode *>,
1189 DebugModelHolder>::iterator it) {
1190 if (!currentCollisionShapes.contains(it.key())) {
1191 auto holder = it.value();
1192 holder.releaseMeshPointer();
1194 delete holder.geometry;
1195 delete holder.model;
1203void QPhysicsWorld::disableDebugDraw()
1205 m_hasIndividualDebugDraw =
false;
1207 for (QAbstractPhysXNode *body : std::as_const(m_physXBodies)) {
1208 const auto &collisionShapes = body->frontendNode->getCollisionShapesList();
1209 const int length = collisionShapes.length();
1210 for (
int idx = 0; idx < length; idx++) {
1211 const auto collisionShape = collisionShapes[idx];
1212 if (collisionShape->enableDebugDraw()) {
1213 m_hasIndividualDebugDraw =
true;
1220void QPhysicsWorld::setEnableCCD(
bool enableCCD)
1222 if (m_enableCCD == enableCCD)
1225 if (m_physicsInitialized) {
1227 <<
"Warning: Changing 'enableCCD' after physics is initialized will have no effect";
1231 m_enableCCD = enableCCD;
1232 emit enableCCDChanged(m_enableCCD);
1235void QPhysicsWorld::setTypicalLength(
float typicalLength)
1237 if (qFuzzyCompare(typicalLength, m_typicalLength))
1240 if (!qIsFinite(typicalLength) || typicalLength <= 0.f) {
1241 qWarning() <<
"Warning: 'typicalLength' value less than zero, ignored";
1245 if (m_physicsInitialized) {
1246 qWarning() <<
"Warning: Changing 'typicalLength' after physics is initialized will have "
1251 m_typicalLength = typicalLength;
1253 emit typicalLengthChanged(typicalLength);
1256void QPhysicsWorld::setTypicalSpeed(
float typicalSpeed)
1258 if (qFuzzyCompare(typicalSpeed, m_typicalSpeed))
1261 if (!qIsFinite(typicalSpeed) || typicalSpeed <= 0.f) {
1262 qWarning() <<
"Warning: 'typicalSpeed' must be finite and greater than zero, ignored";
1266 if (m_physicsInitialized) {
1267 qWarning() <<
"Warning: Changing 'typicalSpeed' after physics is initialized will have "
1272 m_typicalSpeed = typicalSpeed;
1274 emit typicalSpeedChanged(typicalSpeed);
1277float QPhysicsWorld::defaultDensity()
const
1279 return m_defaultDensity;
1282float QPhysicsWorld::minimumTimestep()
const
1284 return m_minTimestep;
1287float QPhysicsWorld::maximumTimestep()
const
1289 return m_maxTimestep;
1292void QPhysicsWorld::setDefaultDensity(
float defaultDensity)
1294 defaultDensity = qBound(0.0000001f, defaultDensity, PX_MAX_F32);
1296 if (qFuzzyCompare(m_defaultDensity, defaultDensity))
1298 m_defaultDensity = defaultDensity;
1301 for (QAbstractPhysXNode *body : std::as_const(m_physXBodies))
1302 body->updateDefaultDensity(m_defaultDensity);
1304 emit defaultDensityChanged(defaultDensity);
1309void QPhysicsWorld::cleanupRemovedNodes()
1311 m_physXBodies.removeIf([
this](QAbstractPhysXNode *body) {
1312 return body->cleanupIfRemoved(m_physx);
1314 m_removedPhysicsNodes.clear();
1317void QPhysicsWorld::cleanupRemovedJoints()
1319 for (physx::PxJoint *joint : m_removedJoints) {
1323 m_removedJoints.clear();
1326void QPhysicsWorld::initPhysics()
1328 Q_ASSERT(!m_physicsInitialized);
1330 const unsigned int numThreads = m_numThreads >= 0 ? m_numThreads : qMax(0, QThread::idealThreadCount());
1331 m_physx->createScene(m_typicalLength, m_typicalSpeed, m_gravity,
this, numThreads);
1332 m_frameAnimator->start();
1333 m_physicsInitialized =
true;
1336void QPhysicsWorld::simulateFrame()
1338 constexpr double MILLIONTH = 0.000001;
1339 constexpr double THOUSANDTH = 0.001;
1341 if (m_inDesignStudio) {
1342 frameFinishedDesignStudio();
1346 if (!m_physx->isRunning) {
1348 m_physx->isRunning =
true;
1349 const double minTimestepSecs = m_minTimestep * 0.001;
1350 m_physx->scene->simulate(minTimestepSecs);
1351 m_currTimeStep = minTimestepSecs;
1356 if (!m_frameFetched && !m_physx->scene->checkResults()) {
1361 if (!m_frameFetched) {
1362 m_physx->scene->fetchResults(
true);
1363 frameFinished(m_currTimeStep);
1364 m_frameFetched =
true;
1365 if (Q_UNLIKELY(!qtPhysicsTimingsFile.isEmpty())) {
1366 const double deltaMS = m_timer.nsecsElapsed() * MILLIONTH;
1367 m_frameTimings.append(deltaMS);
1372 const double deltaMS = m_timer.nsecsElapsed() * MILLIONTH;
1373 if (deltaMS < m_minTimestep)
1375 const double deltaSecs = qMin<
double>(deltaMS, m_maxTimestep) * THOUSANDTH;
1377 m_physx->scene->simulate(deltaSecs);
1378 m_frameFetched =
false;
1379 m_currTimeStep = deltaSecs;
1382void QPhysicsWorld::frameFinished(
float deltaTime)
1385 matchOrphanJoints();
1386 emitContactCallbacks();
1387 cleanupRemovedNodes();
1388 cleanupRemovedJoints();
1390 for (
auto *node : std::as_const(m_newPhysicsNodes)) {
1391 auto *body = node->createPhysXBackend();
1392 body->init(
this, m_physx);
1393 m_physXBodies.push_back(body);
1395 m_newPhysicsNodes.clear();
1397 QHash<QQuick3DNode *, QMatrix4x4> transformCache;
1400 for (
auto *physXBody : std::as_const(m_physXBodies)) {
1401 physXBody->markDirtyShapes();
1402 physXBody->rebuildDirtyShapes(
this, m_physx);
1403 physXBody->updateFilters();
1406 physXBody->sync(deltaTime, transformCache);
1409 for (QPhysicsJoint *joint : std::as_const(m_joints)) {
1410 joint->updatePhysXBackend();
1414 emit frameDone(deltaTime * 1000);
1417void QPhysicsWorld::frameFinishedDesignStudio()
1421 matchOrphanJoints();
1422 emitContactCallbacks();
1423 cleanupRemovedNodes();
1425 m_newPhysicsNodes.clear();
1427 updateDebugDrawDesignStudio();
1430QPhysicsWorld *QPhysicsWorld::getWorld(QQuick3DNode *node)
1432 for (QPhysicsWorld *world : std::as_const(worldManager.worlds)) {
1433 if (!world->m_scene) {
1437 QQuick3DNode *nodeCurr = node;
1440 if (nodeCurr == world->m_scene)
1443 while (nodeCurr->parentNode()) {
1444 nodeCurr = nodeCurr->parentNode();
1445 if (nodeCurr == world->m_scene)
1453QPhysicsWorld *QPhysicsWorld::getWorld(QPhysicsJoint *joint)
1455 for (QPhysicsWorld *world : worldManager.worlds) {
1456 if (!world->m_scene) {
1460 QObject *nodeCurr = joint;
1462 while (nodeCurr->parent()) {
1463 nodeCurr = nodeCurr->parent();
1464 if (nodeCurr == world->m_scene) {
1466 }
else if (
auto view3d = qobject_cast<QQuick3DViewport *>(nodeCurr);
1467 view3d && view3d->scene() == world->m_scene) {
1477void QPhysicsWorld::matchOrphanNodes()
1480 if (worldManager.orphanNodes.isEmpty())
1483 qsizetype numNodes = worldManager.orphanNodes.length();
1486 while (idx < numNodes) {
1487 auto node = worldManager.orphanNodes[idx];
1488 auto world = getWorld(node);
1489 if (world ==
this) {
1490 world->m_newPhysicsNodes.push_back(node);
1492 worldManager.orphanNodes.swapItemsAt(idx, numNodes - 1);
1493 worldManager.orphanNodes.pop_back();
1501void QPhysicsWorld::matchOrphanJoints()
1503 if (worldManager.orphanJoints.isEmpty())
1506 qsizetype numNodes = worldManager.orphanJoints.length();
1509 while (idx < numNodes) {
1510 auto node = worldManager.orphanJoints[idx];
1511 auto world = getWorld(node);
1512 if (world ==
this) {
1513 world->m_joints.push_back(node);
1515 worldManager.orphanJoints.swapItemsAt(idx, numNodes - 1);
1516 worldManager.orphanJoints.pop_back();
1524void QPhysicsWorld::findPhysicsNodes()
1529 if (m_scene ==
nullptr)
1533 QList<QQuick3DObject *> children = m_scene->childItems();
1534 while (!children.empty()) {
1535 auto child = children.takeFirst();
1536 if (
auto converted = qobject_cast<QAbstractPhysicsNode *>(child); converted !=
nullptr) {
1538 if (converted->m_backendObject !=
nullptr) {
1539 qWarning() <<
"Warning: physics node already associated with a backend node.";
1543 m_newPhysicsNodes.push_back(converted);
1544 worldManager.orphanNodes.removeAll(converted);
1546 children.append(child->childItems());
1550void QPhysicsWorld::emitContactCallbacks()
1552 for (
const QPhysicsWorld::BodyContact &contact : std::as_const(m_registeredContacts)) {
1553 if (m_removedPhysicsNodes.contains(contact.sender)
1554 || m_removedPhysicsNodes.contains(contact.receiver))
1556 contact.receiver->registerContact(contact.sender, contact.positions, contact.impulses,
1560 m_registeredContacts.clear();
1563physx::PxPhysics *QPhysicsWorld::getPhysics()
1565 return StaticPhysXObjects::getReference().physics;
1568physx::PxCooking *QPhysicsWorld::getCooking()
1570 return StaticPhysXObjects::getReference().cooking;
1573physx::PxControllerManager *QPhysicsWorld::controllerManager()
1575 if (m_physx->scene && !m_physx->controllerManager) {
1576 m_physx->controllerManager = PxCreateControllerManager(*m_physx->scene);
1577 qCDebug(lcQuick3dPhysics) <<
"Created controller manager" << m_physx->controllerManager;
1579 return m_physx->controllerManager;
1582QQuick3DNode *QPhysicsWorld::scene()
const
1587void QPhysicsWorld::setScene(QQuick3DNode *newScene)
1589 if (m_scene == newScene)
1595 for (
auto body : std::as_const(m_physXBodies)) {
1596 deregisterNode(body->frontendNode);
1600 bool sceneOK =
true;
1601 for (QPhysicsWorld *world : std::as_const(worldManager.worlds)) {
1602 if (world !=
this && world->scene() == newScene) {
1604 qWarning() <<
"Warning: scene already associated with physics world";
1610 emit sceneChanged();
1613int QPhysicsWorld::numThreads()
const
1615 return m_numThreads;
1618void QPhysicsWorld::setNumThreads(
int newNumThreads)
1620 newNumThreads = qMin(newNumThreads, 256);
1622 if (m_numThreads == newNumThreads)
1625 m_numThreads = newNumThreads;
1626 emit numThreadsChanged();
1629bool QPhysicsWorld::reportKinematicKinematicCollisions()
const
1631 return m_reportKinematicKinematicCollisions;
1634void QPhysicsWorld::setReportKinematicKinematicCollisions(
1635 bool newReportKinematicKinematicCollisions)
1637 if (m_reportKinematicKinematicCollisions == newReportKinematicKinematicCollisions)
1639 m_reportKinematicKinematicCollisions = newReportKinematicKinematicCollisions;
1640 emit reportKinematicKinematicCollisionsChanged();
1643bool QPhysicsWorld::reportStaticKinematicCollisions()
const
1645 return m_reportStaticKinematicCollisions;
1648void QPhysicsWorld::setReportStaticKinematicCollisions(
bool newReportStaticKinematicCollisions)
1650 if (m_reportStaticKinematicCollisions == newReportStaticKinematicCollisions)
1652 m_reportStaticKinematicCollisions = newReportStaticKinematicCollisions;
1653 emit reportStaticKinematicCollisionsChanged();
1656QPhysicsWorld::QueryStructure QPhysicsWorld::staticQueryStructure()
const
1658 return m_staticQueryStructure;
1661void QPhysicsWorld::setStaticQueryStructure(QueryStructure newStaticQueryStructure)
1663 if (m_staticQueryStructure == newStaticQueryStructure)
1666 if (newStaticQueryStructure == QueryStructure::NoStructure) {
1668 <<
"Warning: The QueryStructure::NoStructure is not supported for staticQueryStructure. Available options: [StaticTree, DynamicTree]";
1672 if (m_physicsInitialized) {
1674 <<
"Warning: Changing 'staticQueryStructure' after physics is initialized will have no effect";
1678 m_staticQueryStructure = newStaticQueryStructure;
1679 emit staticQueryStructureChanged();
1682QPhysicsWorld::QueryStructure QPhysicsWorld::dynamicQueryStructure()
const
1684 return m_dynamicQueryStructure;
1687void QPhysicsWorld::setDynamicQueryStructure(QueryStructure newDynamicQueryStructure)
1689 if (m_dynamicQueryStructure == newDynamicQueryStructure)
1692 if (m_physicsInitialized) {
1694 <<
"Warning: Changing 'dynamicQueryStructure' after physics is initialized will have no effect";
1698 m_dynamicQueryStructure = newDynamicQueryStructure;
1699 emit dynamicQueryStructureChanged();
1704#include "qphysicsworld.moc"
Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher")
static QWorldManager worldManager
static void collectPhysicsNodes(QQuick3DObject *node, QList< QAbstractPhysicsNode * > &nodes)
static const QString qtPhysicsTimingsFile
#define QT_BEGIN_NAMESPACE
QVector< QAbstractPhysicsNode * > orphanNodes
QVector< QPhysicsWorld * > worlds
QVector< QPhysicsJoint * > orphanJoints