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"
26#include <QtQuick/private/qquickframeanimation_p.h>
27#include <QtQuick3D/private/qquick3dobject_p.h>
28#include <QtQuick3D/private/qquick3dnode_p.h>
29#include <QtQuick3D/private/qquick3dmodel_p.h>
30#include <QtQuick3D/private/qquick3dprincipledmaterial_p.h>
31#include <QtQuick3DUtils/private/qssgutils_p.h>
33#include <QtGui/qquaternion.h>
35#include <QtEnvironmentVariables>
37#define PHYSX_ENABLE_PVD 0
42
43
44
45
46
47
48
49
50
53
54
55
56
57
60
61
62
65
66
67
68
71
72
73
74
75
76
77
78
81
82
83
84
85
86
87
90
91
92
93
94
95
96
97
98
99
102
103
104
105
106
107
108
109
110
111
114
115
116
117
118
119
122
123
124
125
126
127
128
129
130
131
132
135
136
137
138
139
140
141
142
143
144
145
148
149
150
151
152
153
154
155
158
159
160
161
162
163
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
198
199
200
201
202
203
204
205
206
207
208
209
210
213
214
215
216
217
218
219
220
221
222
223
224
225
226
258void QPhysicsWorld::DebugModelHolder::releaseMeshPointer()
260 if (
auto base =
static_cast<physx::PxBase *>(ptr); base)
265const QVector3D &QPhysicsWorld::DebugModelHolder::halfExtents()
const
269void QPhysicsWorld::DebugModelHolder::setHalfExtents(
const QVector3D &halfExtents)
273float QPhysicsWorld::DebugModelHolder::radius()
const
277void QPhysicsWorld::DebugModelHolder::setRadius(
float radius)
281float QPhysicsWorld::DebugModelHolder::heightScale()
const
285void QPhysicsWorld::DebugModelHolder::setHeightScale(
float heightScale)
287 data.setX(heightScale);
289float QPhysicsWorld::DebugModelHolder::halfHeight()
const
293void QPhysicsWorld::DebugModelHolder::setHalfHeight(
float halfHeight)
295 data.setY(halfHeight);
297float QPhysicsWorld::DebugModelHolder::rowScale()
const
301void QPhysicsWorld::DebugModelHolder::setRowScale(
float rowScale)
305float QPhysicsWorld::DebugModelHolder::columnScale()
const
309void QPhysicsWorld::DebugModelHolder::setColumnScale(
float columnScale)
311 data.setZ(columnScale);
313physx::PxConvexMesh *QPhysicsWorld::DebugModelHolder::getConvexMesh()
315 return static_cast<physx::PxConvexMesh *>(ptr);
317void QPhysicsWorld::DebugModelHolder::setConvexMesh(physx::PxConvexMesh *mesh)
319 ptr =
static_cast<
void *>(mesh);
321physx::PxTriangleMesh *QPhysicsWorld::DebugModelHolder::getTriangleMesh()
323 return static_cast<physx::PxTriangleMesh *>(ptr);
325void QPhysicsWorld::DebugModelHolder::setTriangleMesh(physx::PxTriangleMesh *mesh)
327 ptr =
static_cast<
void *>(mesh);
329physx::PxHeightField *QPhysicsWorld::DebugModelHolder::getHeightField()
331 return static_cast<physx::PxHeightField *>(ptr);
333void QPhysicsWorld::DebugModelHolder::setHeightField(physx::PxHeightField *hf)
335 ptr =
static_cast<physx::PxHeightField *>(hf);
349void QPhysicsWorld::registerNode(QAbstractPhysicsNode *physicsNode)
351 auto world = getWorld(physicsNode);
353 world->m_newPhysicsNodes.push_back(physicsNode);
355 worldManager.orphanNodes.push_back(physicsNode);
359void QPhysicsWorld::deregisterNode(QAbstractPhysicsNode *physicsNode)
361 for (
auto world : std::as_const(worldManager.worlds)) {
362 world->m_newPhysicsNodes.removeAll(physicsNode);
363 QMutexLocker locker(&world->m_removedPhysicsNodesMutex);
364 if (physicsNode->m_backendObject) {
365 Q_ASSERT(physicsNode->m_backendObject->frontendNode == physicsNode);
366 physicsNode->m_backendObject->frontendNode =
nullptr;
367 physicsNode->m_backendObject->isRemoved =
true;
368 physicsNode->m_backendObject =
nullptr;
370 world->m_removedPhysicsNodes.insert(physicsNode);
372 worldManager.orphanNodes.removeAll(physicsNode);
375void QPhysicsWorld::registerJoint(QPhysicsJoint *joint)
377 auto world = getWorld(joint);
379 world->m_joints.push_back(joint);
381 worldManager.orphanJoints.push_back(joint);
385void QPhysicsWorld::deregisterJoint(QPhysicsJoint *joint)
387 for (
auto world : worldManager.worlds) {
388 QMutexLocker locker(&world->m_removedPhysicsNodesMutex);
389 world->m_removedJoints.insert(joint->getPhysXBackend());
392 qsizetype idx = world->m_joints.indexOf(joint);
394 world->m_joints.swapItemsAt(idx, world->m_joints.size() - 1);
395 world->m_joints.pop_back();
398 worldManager.orphanJoints.removeAll(joint);
401void QPhysicsWorld::registerContact(QAbstractPhysicsNode *sender, QAbstractPhysicsNode *receiver,
402 const QVector<QVector3D> &positions,
403 const QVector<QVector3D> &impulses,
404 const QVector<QVector3D> &normals)
413 contact.sender = sender;
414 contact.receiver = receiver;
415 contact.positions = positions;
416 contact.impulses = impulses;
417 contact.normals = normals;
419 m_registeredContacts.push_back(contact);
422QPhysicsWorld::QPhysicsWorld(QObject *parent) : QObject(parent)
424 m_inDesignStudio = !qEnvironmentVariableIsEmpty(
"QML_PUPPET_MODE");
425 m_physx =
new QPhysXWorld;
426 m_physx->createWorld();
428 worldManager.worlds.push_back(
this);
432 m_frameAnimator =
new FrameAnimator;
433 connect(m_frameAnimator, &QQuickFrameAnimation::triggered,
this,
434 &QPhysicsWorld::simulateFrame);
437QPhysicsWorld::~QPhysicsWorld()
439 if (m_frameAnimator) {
440 m_frameAnimator->stop();
441 delete m_frameAnimator;
445 m_physx->scene->fetchResults(
true);
447 for (
auto body : std::as_const(m_physXBodies)) {
448 body->cleanup(m_physx);
451 m_physXBodies.clear();
452 m_physx->deleteWorld();
454 worldManager.worlds.removeAll(
this);
456 if (!qtPhysicsTimingsFile.isEmpty()) {
457 if (m_frameTimings.isEmpty()) {
458 qWarning() <<
"No frame timings saved.";
459 }
else if (
auto csvFile = QFile(qtPhysicsTimingsFile); csvFile.open(QIODevice::WriteOnly)) {
460 QTextStream out(&csvFile);
461 for (
int i = 1; i < m_frameTimings.size(); i++) {
462 out << i <<
"," << m_frameTimings[i] <<
'\n';
466 qWarning() <<
"Could not open timings file " << qtPhysicsTimingsFile;
471void QPhysicsWorld::classBegin() {}
473void QPhysicsWorld::componentComplete()
475 if ((!m_running && !m_inDesignStudio) || m_physicsInitialized)
480QVector3D QPhysicsWorld::gravity()
const
485bool QPhysicsWorld::running()
const
490bool QPhysicsWorld::forceDebugDraw()
const
492 return m_forceDebugDraw;
495bool QPhysicsWorld::enableCCD()
const
500float QPhysicsWorld::typicalLength()
const
502 return m_typicalLength;
505float QPhysicsWorld::typicalSpeed()
const
507 return m_typicalSpeed;
510bool QPhysicsWorld::isNodeRemoved(QAbstractPhysicsNode *object)
512 return m_removedPhysicsNodes.contains(object);
515void QPhysicsWorld::setGravity(QVector3D gravity)
517 if (m_gravity == gravity)
521 if (m_physx->scene) {
522 m_physx->scene->setGravity(QPhysicsUtils::toPhysXType(m_gravity));
524 emit gravityChanged(m_gravity);
527void QPhysicsWorld::setRunning(
bool running)
529 if (m_running == running)
533 if (!m_inDesignStudio && m_running && !m_physicsInitialized)
537 m_frameAnimator->start();
539 m_frameAnimator->stop();
541 emit runningChanged(m_running);
544void QPhysicsWorld::setForceDebugDraw(
bool forceDebugDraw)
546 if (m_forceDebugDraw == forceDebugDraw)
549 m_forceDebugDraw = forceDebugDraw;
550 if (!m_forceDebugDraw)
554 emit forceDebugDrawChanged(m_forceDebugDraw);
557QQuick3DNode *QPhysicsWorld::viewport()
const
562void QPhysicsWorld::setHasIndividualDebugDraw()
564 m_hasIndividualDebugDraw =
true;
567void QPhysicsWorld::setViewport(QQuick3DNode *viewport)
569 if (m_viewport == viewport)
572 m_viewport = viewport;
575 for (
auto material : std::as_const(m_debugMaterials))
577 m_debugMaterials.clear();
579 for (
auto &holder : m_collisionShapeDebugModels) {
580 holder.releaseMeshPointer();
583 m_collisionShapeDebugModels.clear();
585 emit viewportChanged(m_viewport);
588void QPhysicsWorld::setMinimumTimestep(
float minTimestep)
590 if (qFuzzyCompare(m_minTimestep, minTimestep))
593 if (minTimestep > m_maxTimestep) {
594 qWarning(
"Minimum timestep greater than maximum timestep, value clamped");
595 minTimestep = qMin(minTimestep, m_maxTimestep);
598 if (minTimestep < 0.f) {
599 qWarning(
"Minimum timestep less than zero, value clamped");
600 minTimestep = qMax(minTimestep, 0.f);
603 if (qFuzzyCompare(m_minTimestep, minTimestep))
606 m_minTimestep = minTimestep;
607 emit minimumTimestepChanged(m_minTimestep);
610void QPhysicsWorld::setMaximumTimestep(
float maxTimestep)
612 if (qFuzzyCompare(m_maxTimestep, maxTimestep))
615 if (maxTimestep < 0.f) {
616 qWarning(
"Maximum timestep less than zero, value clamped");
617 maxTimestep = qMax(maxTimestep, 0.f);
620 if (qFuzzyCompare(m_maxTimestep, maxTimestep))
623 m_maxTimestep = maxTimestep;
624 emit maximumTimestepChanged(maxTimestep);
627void QPhysicsWorld::setupDebugMaterials(QQuick3DNode *sceneNode)
629 if (!m_debugMaterials.isEmpty())
632 const int lineWidth = m_inDesignStudio ? 1 : 3;
635 for (
auto color : { QColorConstants::Svg::chartreuse, QColorConstants::Svg::cyan,
636 QColorConstants::Svg::lightsalmon, QColorConstants::Svg::red,
637 QColorConstants::Svg::blueviolet, QColorConstants::Svg::black }) {
638 auto debugMaterial =
new QQuick3DPrincipledMaterial();
639 debugMaterial->setLineWidth(lineWidth);
640 debugMaterial->setParentItem(sceneNode);
641 debugMaterial->setParent(sceneNode);
642 debugMaterial->setBaseColor(color);
643 debugMaterial->setLighting(QQuick3DPrincipledMaterial::NoLighting);
644 debugMaterial->setCullMode(QQuick3DMaterial::NoCulling);
645 m_debugMaterials.push_back(debugMaterial);
649void QPhysicsWorld::updateDebugDraw()
651 if (!(m_forceDebugDraw || m_hasIndividualDebugDraw)) {
653 for (
auto &holder : m_collisionShapeDebugModels) {
654 holder.releaseMeshPointer();
657 m_collisionShapeDebugModels.clear();
662 auto sceneNode = m_viewport ? m_viewport : m_scene;
664 if (sceneNode ==
nullptr)
667 setupDebugMaterials(sceneNode);
668 m_hasIndividualDebugDraw =
false;
671 QSet<QPair<QAbstractCollisionShape *, QAbstractPhysXNode *>> currentCollisionShapes;
672 currentCollisionShapes.reserve(m_collisionShapeDebugModels.size());
674 for (QAbstractPhysXNode *node : std::as_const(m_physXBodies)) {
675 if (!node->debugGeometryCapability())
678 const auto &collisionShapes = node->frontendNode->getCollisionShapesList();
679 const int materialIdx =
static_cast<
int>(node->getDebugDrawBodyType());
680 const int length = collisionShapes.length();
681 for (
int idx = 0; idx < length; idx++) {
682 const auto collisionShape = collisionShapes[idx];
684 if (!m_forceDebugDraw && !collisionShape->enableDebugDraw())
687 DebugModelHolder &holder =
688 m_collisionShapeDebugModels[std::make_pair(collisionShape, node)];
689 auto &model = holder.model;
691 currentCollisionShapes.insert(std::make_pair(collisionShape, node));
693 m_hasIndividualDebugDraw =
694 m_hasIndividualDebugDraw || collisionShape->enableDebugDraw();
698 model =
new QQuick3DModel();
699 model->setParentItem(sceneNode);
700 model->setParent(sceneNode);
701 model->setCastsShadows(
false);
702 model->setReceivesShadows(
false);
703 model->setCastsReflections(
false);
706 model->setVisible(
true);
709 auto material = m_debugMaterials[materialIdx];
710 QQmlListReference materialsRef(model,
"materials");
711 if (materialsRef.count() == 0 || materialsRef.at(0) != material) {
712 materialsRef.clear();
713 materialsRef.append(material);
719 if (qobject_cast<QCharacterController *>(node->frontendNode)) {
720 QCapsuleShape *capsuleShape = qobject_cast<QCapsuleShape *>(collisionShape);
724 const float radius = capsuleShape->diameter() * 0.5;
725 const float halfHeight = capsuleShape->height() * 0.5;
727 if (!qFuzzyCompare(radius, holder.radius())
728 || !qFuzzyCompare(halfHeight, holder.halfHeight())) {
729 auto geom = QDebugDrawHelper::generateCapsuleGeometry(radius, halfHeight);
730 geom->setParent(model);
731 model->setGeometry(geom);
732 holder.setRadius(radius);
733 holder.setHalfHeight(halfHeight);
736 model->setPosition(node->frontendNode->scenePosition());
737 model->setRotation(node->frontendNode->sceneRotation()
738 * QQuaternion::fromEulerAngles(0, 0, 90));
742 if (node->shapes.length() < length)
745 const auto physXShape = node->shapes[idx];
746 auto localPose = physXShape->getLocalPose();
748 switch (physXShape->getGeometryType()) {
749 case physx::PxGeometryType::eBOX: {
750 physx::PxBoxGeometry boxGeometry;
751 physXShape->getBoxGeometry(boxGeometry);
752 const auto &halfExtentsOld = holder.halfExtents();
753 const auto halfExtents = QPhysicsUtils::toQtType(boxGeometry.halfExtents);
754 if (!qFuzzyCompare(halfExtentsOld, halfExtents)) {
755 auto geom = QDebugDrawHelper::generateBoxGeometry(halfExtents);
756 geom->setParent(model);
757 model->setGeometry(geom);
758 holder.setHalfExtents(halfExtents);
764 case physx::PxGeometryType::eSPHERE: {
765 physx::PxSphereGeometry sphereGeometry;
766 physXShape->getSphereGeometry(sphereGeometry);
767 const float radius = holder.radius();
768 if (!qFuzzyCompare(sphereGeometry.radius, radius)) {
769 auto geom = QDebugDrawHelper::generateSphereGeometry(sphereGeometry.radius);
770 geom->setParent(model);
771 model->setGeometry(geom);
772 holder.setRadius(sphereGeometry.radius);
777 case physx::PxGeometryType::eCAPSULE: {
778 physx::PxCapsuleGeometry capsuleGeometry;
779 physXShape->getCapsuleGeometry(capsuleGeometry);
780 const float radius = holder.radius();
781 const float halfHeight = holder.halfHeight();
783 if (!qFuzzyCompare(capsuleGeometry.radius, radius)
784 || !qFuzzyCompare(capsuleGeometry.halfHeight, halfHeight)) {
785 auto geom = QDebugDrawHelper::generateCapsuleGeometry(
786 capsuleGeometry.radius, capsuleGeometry.halfHeight);
787 geom->setParent(model);
788 model->setGeometry(geom);
789 holder.setRadius(capsuleGeometry.radius);
790 holder.setHalfHeight(capsuleGeometry.halfHeight);
795 case physx::PxGeometryType::ePLANE:{
796 physx::PxPlaneGeometry planeGeometry;
797 physXShape->getPlaneGeometry(planeGeometry);
799 const QQuaternion rotation =
800 QPhysicsUtils::kMinus90YawRotation * QPhysicsUtils::toQtType(localPose.q);
801 localPose = physx::PxTransform(localPose.p, QPhysicsUtils::toPhysXType(rotation));
803 if (model->geometry() ==
nullptr) {
804 auto geom = QDebugDrawHelper::generatePlaneGeometry();
805 geom->setParent(model);
806 model->setGeometry(geom);
814 case physx::PxGeometryType::eHEIGHTFIELD: {
815 physx::PxHeightFieldGeometry heightFieldGeometry;
816 bool success = physXShape->getHeightFieldGeometry(heightFieldGeometry);
818 const float heightScale = holder.heightScale();
819 const float rowScale = holder.rowScale();
820 const float columnScale = holder.columnScale();
822 if (
auto heightField = holder.getHeightField();
823 heightField && heightField != heightFieldGeometry.heightField) {
824 heightField->release();
825 holder.setHeightField(
nullptr);
828 if (!qFuzzyCompare(heightFieldGeometry.heightScale, heightScale)
829 || !qFuzzyCompare(heightFieldGeometry.rowScale, rowScale)
830 || !qFuzzyCompare(heightFieldGeometry.columnScale, columnScale)
831 || !holder.getHeightField()) {
832 if (!holder.getHeightField()) {
833 heightFieldGeometry.heightField->acquireReference();
834 holder.setHeightField(heightFieldGeometry.heightField);
836 auto geom = QDebugDrawHelper::generateHeightFieldGeometry(
837 heightFieldGeometry.heightField, heightFieldGeometry.heightScale,
838 heightFieldGeometry.rowScale, heightFieldGeometry.columnScale);
839 geom->setParent(model);
840 model->setGeometry(geom);
841 holder.setHeightScale(heightFieldGeometry.heightScale);
842 holder.setRowScale(heightFieldGeometry.rowScale);
843 holder.setColumnScale(heightFieldGeometry.columnScale);
848 case physx::PxGeometryType::eCONVEXMESH: {
849 physx::PxConvexMeshGeometry convexMeshGeometry;
850 const bool success = physXShape->getConvexMeshGeometry(convexMeshGeometry);
852 const auto rotation = convexMeshGeometry.scale.rotation * localPose.q;
853 localPose = physx::PxTransform(localPose.p, rotation);
854 model->setScale(QPhysicsUtils::toQtType(convexMeshGeometry.scale.scale));
856 if (
auto convexMesh = holder.getConvexMesh();
857 convexMesh && convexMesh != convexMeshGeometry.convexMesh) {
858 convexMesh->release();
859 holder.setConvexMesh(
nullptr);
862 if (!model->geometry() || !holder.getConvexMesh()) {
863 if (!holder.getConvexMesh()) {
864 convexMeshGeometry.convexMesh->acquireReference();
865 holder.setConvexMesh(convexMeshGeometry.convexMesh);
867 auto geom = QDebugDrawHelper::generateConvexMeshGeometry(
868 convexMeshGeometry.convexMesh);
869 geom->setParent(model);
870 model->setGeometry(geom);
875 case physx::PxGeometryType::eTRIANGLEMESH: {
876 physx::PxTriangleMeshGeometry triangleMeshGeometry;
877 const bool success = physXShape->getTriangleMeshGeometry(triangleMeshGeometry);
879 const auto rotation = triangleMeshGeometry.scale.rotation * localPose.q;
880 localPose = physx::PxTransform(localPose.p, rotation);
881 model->setScale(QPhysicsUtils::toQtType(triangleMeshGeometry.scale.scale));
883 if (
auto triangleMesh = holder.getTriangleMesh();
884 triangleMesh && triangleMesh != triangleMeshGeometry.triangleMesh) {
885 triangleMesh->release();
886 holder.setTriangleMesh(
nullptr);
889 if (!model->geometry() || !holder.getTriangleMesh()) {
890 if (!holder.getTriangleMesh()) {
891 triangleMeshGeometry.triangleMesh->acquireReference();
892 holder.setTriangleMesh(triangleMeshGeometry.triangleMesh);
894 auto geom = QDebugDrawHelper::generateTriangleMeshGeometry(
895 triangleMeshGeometry.triangleMesh);
896 geom->setParent(model);
897 model->setGeometry(geom);
902 case physx::PxGeometryType::eINVALID:
903 case physx::PxGeometryType::eGEOMETRY_COUNT:
908 auto globalPose = node->getGlobalPose();
909 auto finalPose = globalPose.transform(localPose);
911 model->setRotation(QPhysicsUtils::toQtType(finalPose.q));
912 model->setPosition(QPhysicsUtils::toQtType(finalPose.p));
917 m_collisionShapeDebugModels.removeIf(
918 [&](QHash<QPair<QAbstractCollisionShape *, QAbstractPhysXNode *>,
919 DebugModelHolder>::iterator it) {
920 if (!currentCollisionShapes.contains(it.key())) {
921 auto holder = it.value();
922 holder.releaseMeshPointer();
933 if (
auto shape = qobject_cast<QAbstractPhysicsNode *>(node)) {
934 nodes.push_back(shape);
938 auto childItems = node->childItems();
939 for (QQuick3DObject *child : std::as_const(childItems))
940 collectPhysicsNodes(child, nodes);
943void QPhysicsWorld::updateDebugDrawDesignStudio()
946 auto sceneNode = m_viewport ? m_viewport : m_scene;
948 if (sceneNode ==
nullptr)
951 setupDebugMaterials(sceneNode);
954 QSet<QPair<QAbstractCollisionShape *, QAbstractPhysicsNode *>> currentCollisionShapes;
955 currentCollisionShapes.reserve(m_collisionShapeDebugModels.size());
957 QList<QAbstractPhysicsNode *> activePhysicsNodes;
958 activePhysicsNodes.reserve(m_collisionShapeDebugModels.size());
959 collectPhysicsNodes(m_scene, activePhysicsNodes);
961 for (QAbstractPhysicsNode *node : std::as_const(activePhysicsNodes)) {
963 const auto &collisionShapes = node->getCollisionShapesList();
964 const int materialIdx = 0;
965 const int length = collisionShapes.length();
967 const bool isCharacterController = qobject_cast<QCharacterController *>(node) !=
nullptr;
969 for (
int idx = 0; idx < length; idx++) {
970 QAbstractCollisionShape *collisionShape = collisionShapes[idx];
971 DebugModelHolder &holder =
972 m_DesignStudioDebugModels[std::make_pair(collisionShape, node)];
973 auto &model = holder.model;
975 currentCollisionShapes.insert(std::make_pair(collisionShape, node));
977 m_hasIndividualDebugDraw =
978 m_hasIndividualDebugDraw || collisionShape->enableDebugDraw();
985 model =
new QQuick3DModel();
986 model->setParentItem(sceneNode);
987 model->setParent(sceneNode);
988 model->setCastsShadows(
false);
989 model->setReceivesShadows(
false);
990 model->setCastsReflections(
false);
993 const bool hasGeometry = holder.geometry !=
nullptr;
994 QVector3D scenePosition = collisionShape->scenePosition();
995 QQuaternion sceneRotation = collisionShape->sceneRotation();
996 QQuick3DGeometry *newGeometry =
nullptr;
998 if (isCharacterController)
999 sceneRotation = sceneRotation * QQuaternion::fromEulerAngles(QVector3D(0, 0, 90));
1002 auto material = m_debugMaterials[materialIdx];
1003 QQmlListReference materialsRef(model,
"materials");
1004 if (materialsRef.count() == 0 || materialsRef.at(0) != material) {
1005 materialsRef.clear();
1006 materialsRef.append(material);
1010 if (
auto shape = qobject_cast<QBoxShape *>(collisionShape)) {
1011 const auto &halfExtentsOld = holder.halfExtents();
1012 const auto halfExtents = shape->sceneScale() * shape->extents() * 0.5f;
1013 if (!qFuzzyCompare(halfExtentsOld, halfExtents) || !hasGeometry) {
1014 newGeometry = QDebugDrawHelper::generateBoxGeometry(halfExtents);
1015 holder.setHalfExtents(halfExtents);
1017 }
else if (
auto shape = qobject_cast<QSphereShape *>(collisionShape)) {
1018 const float radiusOld = holder.radius();
1019 const float radius = shape->sceneScale().x() * shape->diameter() * 0.5f;
1020 if (!qFuzzyCompare(radiusOld, radius) || !hasGeometry) {
1021 newGeometry = QDebugDrawHelper::generateSphereGeometry(radius);
1022 holder.setRadius(radius);
1024 }
else if (
auto shape = qobject_cast<QCapsuleShape *>(collisionShape)) {
1025 const float radiusOld = holder.radius();
1026 const float halfHeightOld = holder.halfHeight();
1027 const float radius = shape->sceneScale().y() * shape->diameter() * 0.5f;
1028 const float halfHeight = shape->sceneScale().x() * shape->height() * 0.5f;
1030 if ((!qFuzzyCompare(radiusOld, radius) || !qFuzzyCompare(halfHeightOld, halfHeight))
1032 newGeometry = QDebugDrawHelper::generateCapsuleGeometry(radius, halfHeight);
1033 holder.setRadius(radius);
1034 holder.setHalfHeight(halfHeight);
1036 }
else if (qobject_cast<QPlaneShape *>(collisionShape)) {
1038 newGeometry = QDebugDrawHelper::generatePlaneGeometry();
1039 }
else if (
auto shape = qobject_cast<QHeightFieldShape *>(collisionShape)) {
1040 physx::PxHeightFieldGeometry *heightFieldGeometry =
1041 static_cast<physx::PxHeightFieldGeometry *>(shape->getPhysXGeometry());
1042 const float heightScale = holder.heightScale();
1043 const float rowScale = holder.rowScale();
1044 const float columnScale = holder.columnScale();
1045 scenePosition += shape->hfOffset();
1046 if (!heightFieldGeometry) {
1047 qWarning() <<
"Could not get height field";
1048 }
else if (!qFuzzyCompare(heightFieldGeometry->heightScale, heightScale)
1049 || !qFuzzyCompare(heightFieldGeometry->rowScale, rowScale)
1050 || !qFuzzyCompare(heightFieldGeometry->columnScale, columnScale)
1052 newGeometry = QDebugDrawHelper::generateHeightFieldGeometry(
1053 heightFieldGeometry->heightField, heightFieldGeometry->heightScale,
1054 heightFieldGeometry->rowScale, heightFieldGeometry->columnScale);
1055 holder.setHeightScale(heightFieldGeometry->heightScale);
1056 holder.setRowScale(heightFieldGeometry->rowScale);
1057 holder.setColumnScale(heightFieldGeometry->columnScale);
1059 }
else if (
auto shape = qobject_cast<QConvexMeshShape *>(collisionShape)) {
1060 auto convexMeshGeometry =
1061 static_cast<physx::PxConvexMeshGeometry *>(shape->getPhysXGeometry());
1062 if (!convexMeshGeometry) {
1063 qWarning() <<
"Could not get convex mesh";
1065 model->setScale(QPhysicsUtils::toQtType(convexMeshGeometry->scale.scale));
1068 newGeometry = QDebugDrawHelper::generateConvexMeshGeometry(
1069 convexMeshGeometry->convexMesh);
1072 }
else if (
auto shape = qobject_cast<QTriangleMeshShape *>(collisionShape)) {
1073 physx::PxTriangleMeshGeometry *triangleMeshGeometry =
1074 static_cast<physx::PxTriangleMeshGeometry *>(shape->getPhysXGeometry());
1075 if (!triangleMeshGeometry) {
1076 qWarning() <<
"Could not get triangle mesh";
1078 model->setScale(QPhysicsUtils::toQtType(triangleMeshGeometry->scale.scale));
1081 newGeometry = QDebugDrawHelper::generateTriangleMeshGeometry(
1082 triangleMeshGeometry->triangleMesh);
1088 delete holder.geometry;
1089 holder.geometry = newGeometry;
1092 model->setGeometry(holder.geometry);
1093 model->setVisible(
true);
1095 model->setRotation(sceneRotation);
1096 model->setPosition(scenePosition);
1101 m_DesignStudioDebugModels.removeIf(
1102 [&](QHash<QPair<QAbstractCollisionShape *, QAbstractPhysicsNode *>,
1103 DebugModelHolder>::iterator it) {
1104 if (!currentCollisionShapes.contains(it.key())) {
1105 auto holder = it.value();
1106 holder.releaseMeshPointer();
1108 delete holder.geometry;
1109 delete holder.model;
1117void QPhysicsWorld::disableDebugDraw()
1119 m_hasIndividualDebugDraw =
false;
1121 for (QAbstractPhysXNode *body : std::as_const(m_physXBodies)) {
1122 const auto &collisionShapes = body->frontendNode->getCollisionShapesList();
1123 const int length = collisionShapes.length();
1124 for (
int idx = 0; idx < length; idx++) {
1125 const auto collisionShape = collisionShapes[idx];
1126 if (collisionShape->enableDebugDraw()) {
1127 m_hasIndividualDebugDraw =
true;
1134void QPhysicsWorld::setEnableCCD(
bool enableCCD)
1136 if (m_enableCCD == enableCCD)
1139 if (m_physicsInitialized) {
1141 <<
"Warning: Changing 'enableCCD' after physics is initialized will have no effect";
1145 m_enableCCD = enableCCD;
1146 emit enableCCDChanged(m_enableCCD);
1149void QPhysicsWorld::setTypicalLength(
float typicalLength)
1151 if (qFuzzyCompare(typicalLength, m_typicalLength))
1154 if (typicalLength <= 0.f) {
1155 qWarning() <<
"Warning: 'typicalLength' value less than zero, ignored";
1159 if (m_physicsInitialized) {
1160 qWarning() <<
"Warning: Changing 'typicalLength' after physics is initialized will have "
1165 m_typicalLength = typicalLength;
1167 emit typicalLengthChanged(typicalLength);
1170void QPhysicsWorld::setTypicalSpeed(
float typicalSpeed)
1172 if (qFuzzyCompare(typicalSpeed, m_typicalSpeed))
1175 if (m_physicsInitialized) {
1176 qWarning() <<
"Warning: Changing 'typicalSpeed' after physics is initialized will have "
1181 m_typicalSpeed = typicalSpeed;
1183 emit typicalSpeedChanged(typicalSpeed);
1186float QPhysicsWorld::defaultDensity()
const
1188 return m_defaultDensity;
1191float QPhysicsWorld::minimumTimestep()
const
1193 return m_minTimestep;
1196float QPhysicsWorld::maximumTimestep()
const
1198 return m_maxTimestep;
1201void QPhysicsWorld::setDefaultDensity(
float defaultDensity)
1203 if (qFuzzyCompare(m_defaultDensity, defaultDensity))
1205 m_defaultDensity = defaultDensity;
1208 for (QAbstractPhysXNode *body : std::as_const(m_physXBodies))
1209 body->updateDefaultDensity(m_defaultDensity);
1211 emit defaultDensityChanged(defaultDensity);
1216void QPhysicsWorld::cleanupRemovedNodes()
1218 m_physXBodies.removeIf([
this](QAbstractPhysXNode *body) {
1219 return body->cleanupIfRemoved(m_physx);
1221 m_removedPhysicsNodes.clear();
1224void QPhysicsWorld::cleanupRemovedJoints()
1226 for (physx::PxJoint *joint : m_removedJoints) {
1230 m_removedJoints.clear();
1233void QPhysicsWorld::initPhysics()
1235 Q_ASSERT(!m_physicsInitialized);
1237 const unsigned int numThreads = m_numThreads >= 0 ? m_numThreads : qMax(0, QThread::idealThreadCount());
1238 m_physx->createScene(m_typicalLength, m_typicalSpeed, m_gravity, m_enableCCD,
this, numThreads);
1239 m_frameAnimator->start();
1240 m_physicsInitialized =
true;
1243void QPhysicsWorld::simulateFrame()
1245 constexpr double MILLIONTH = 0.000001;
1246 constexpr double THOUSANDTH = 0.001;
1248 if (m_inDesignStudio) {
1249 frameFinishedDesignStudio();
1253 if (!m_physx->isRunning) {
1255 m_physx->isRunning =
true;
1256 const double minTimestepSecs = m_minTimestep * 0.001;
1257 m_physx->scene->simulate(minTimestepSecs);
1258 m_currTimeStep = minTimestepSecs;
1263 if (!m_frameFetched && !m_physx->scene->checkResults()) {
1268 if (!m_frameFetched) {
1269 m_physx->scene->fetchResults(
true);
1270 frameFinished(m_currTimeStep);
1271 m_frameFetched =
true;
1272 if (Q_UNLIKELY(!qtPhysicsTimingsFile.isEmpty())) {
1273 const double deltaMS = m_timer.nsecsElapsed() * MILLIONTH;
1274 m_frameTimings.append(deltaMS);
1279 const double deltaMS = m_timer.nsecsElapsed() * MILLIONTH;
1280 if (deltaMS < m_minTimestep)
1282 const double deltaSecs = qMin<
double>(deltaMS, m_maxTimestep) * THOUSANDTH;
1284 m_physx->scene->simulate(deltaSecs);
1285 m_frameFetched =
false;
1286 m_currTimeStep = deltaSecs;
1289void QPhysicsWorld::frameFinished(
float deltaTime)
1292 matchOrphanJoints();
1293 emitContactCallbacks();
1294 cleanupRemovedNodes();
1295 cleanupRemovedJoints();
1297 for (
auto *node : std::as_const(m_newPhysicsNodes)) {
1298 auto *body = node->createPhysXBackend();
1299 body->init(
this, m_physx);
1300 m_physXBodies.push_back(body);
1302 m_newPhysicsNodes.clear();
1304 QHash<QQuick3DNode *, QMatrix4x4> transformCache;
1307 for (
auto *physXBody : std::as_const(m_physXBodies)) {
1308 physXBody->markDirtyShapes();
1309 physXBody->rebuildDirtyShapes(
this, m_physx);
1310 physXBody->updateFilters();
1313 physXBody->sync(deltaTime, transformCache);
1316 for (QPhysicsJoint *joint : std::as_const(m_joints)) {
1317 joint->updatePhysXBackend();
1321 emit frameDone(deltaTime * 1000);
1324void QPhysicsWorld::frameFinishedDesignStudio()
1328 matchOrphanJoints();
1329 emitContactCallbacks();
1330 cleanupRemovedNodes();
1332 m_newPhysicsNodes.clear();
1334 updateDebugDrawDesignStudio();
1337QPhysicsWorld *QPhysicsWorld::getWorld(QQuick3DNode *node)
1339 for (QPhysicsWorld *world : std::as_const(worldManager.worlds)) {
1340 if (!world->m_scene) {
1344 QQuick3DNode *nodeCurr = node;
1347 if (nodeCurr == world->m_scene)
1350 while (nodeCurr->parentNode()) {
1351 nodeCurr = nodeCurr->parentNode();
1352 if (nodeCurr == world->m_scene)
1360QPhysicsWorld *QPhysicsWorld::getWorld(QPhysicsJoint *joint)
1362 for (QPhysicsWorld *world : worldManager.worlds) {
1363 if (!world->m_scene) {
1367 QObject *nodeCurr = joint;
1369 while (nodeCurr->parent()) {
1370 nodeCurr = nodeCurr->parent();
1371 if (nodeCurr == world->m_scene) {
1373 }
else if (
auto view3d = qobject_cast<QQuick3DViewport *>(nodeCurr);
1374 view3d && view3d->scene() == world->m_scene) {
1384void QPhysicsWorld::matchOrphanNodes()
1387 if (worldManager.orphanNodes.isEmpty())
1390 qsizetype numNodes = worldManager.orphanNodes.length();
1393 while (idx < numNodes) {
1394 auto node = worldManager.orphanNodes[idx];
1395 auto world = getWorld(node);
1396 if (world ==
this) {
1397 world->m_newPhysicsNodes.push_back(node);
1399 worldManager.orphanNodes.swapItemsAt(idx, numNodes - 1);
1400 worldManager.orphanNodes.pop_back();
1408void QPhysicsWorld::matchOrphanJoints()
1410 if (worldManager.orphanJoints.isEmpty())
1413 qsizetype numNodes = worldManager.orphanJoints.length();
1416 while (idx < numNodes) {
1417 auto node = worldManager.orphanJoints[idx];
1418 auto world = getWorld(node);
1419 if (world ==
this) {
1420 world->m_joints.push_back(node);
1422 worldManager.orphanJoints.swapItemsAt(idx, numNodes - 1);
1423 worldManager.orphanJoints.pop_back();
1431void QPhysicsWorld::findPhysicsNodes()
1436 if (m_scene ==
nullptr)
1440 QList<QQuick3DObject *> children = m_scene->childItems();
1441 while (!children.empty()) {
1442 auto child = children.takeFirst();
1443 if (
auto converted = qobject_cast<QAbstractPhysicsNode *>(child); converted !=
nullptr) {
1445 if (converted->m_backendObject !=
nullptr) {
1446 qWarning() <<
"Warning: physics node already associated with a backend node.";
1450 m_newPhysicsNodes.push_back(converted);
1451 worldManager.orphanNodes.removeAll(converted);
1453 children.append(child->childItems());
1457void QPhysicsWorld::emitContactCallbacks()
1459 for (
const QPhysicsWorld::BodyContact &contact : std::as_const(m_registeredContacts)) {
1460 if (m_removedPhysicsNodes.contains(contact.sender)
1461 || m_removedPhysicsNodes.contains(contact.receiver))
1463 contact.receiver->registerContact(contact.sender, contact.positions, contact.impulses,
1467 m_registeredContacts.clear();
1470physx::PxPhysics *QPhysicsWorld::getPhysics()
1472 return StaticPhysXObjects::getReference().physics;
1475physx::PxCooking *QPhysicsWorld::getCooking()
1477 return StaticPhysXObjects::getReference().cooking;
1480physx::PxControllerManager *QPhysicsWorld::controllerManager()
1482 if (m_physx->scene && !m_physx->controllerManager) {
1483 m_physx->controllerManager = PxCreateControllerManager(*m_physx->scene);
1484 qCDebug(lcQuick3dPhysics) <<
"Created controller manager" << m_physx->controllerManager;
1486 return m_physx->controllerManager;
1489QQuick3DNode *QPhysicsWorld::scene()
const
1494void QPhysicsWorld::setScene(QQuick3DNode *newScene)
1496 if (m_scene == newScene)
1502 for (
auto body : std::as_const(m_physXBodies)) {
1503 deregisterNode(body->frontendNode);
1507 bool sceneOK =
true;
1508 for (QPhysicsWorld *world : std::as_const(worldManager.worlds)) {
1509 if (world !=
this && world->scene() == newScene) {
1511 qWarning() <<
"Warning: scene already associated with physics world";
1517 emit sceneChanged();
1520int QPhysicsWorld::numThreads()
const
1522 return m_numThreads;
1525void QPhysicsWorld::setNumThreads(
int newNumThreads)
1527 if (m_numThreads == newNumThreads)
1529 m_numThreads = newNumThreads;
1530 emit numThreadsChanged();
1533bool QPhysicsWorld::reportKinematicKinematicCollisions()
const
1535 return m_reportKinematicKinematicCollisions;
1538void QPhysicsWorld::setReportKinematicKinematicCollisions(
1539 bool newReportKinematicKinematicCollisions)
1541 if (m_reportKinematicKinematicCollisions == newReportKinematicKinematicCollisions)
1543 m_reportKinematicKinematicCollisions = newReportKinematicKinematicCollisions;
1544 emit reportKinematicKinematicCollisionsChanged();
1547bool QPhysicsWorld::reportStaticKinematicCollisions()
const
1549 return m_reportStaticKinematicCollisions;
1552void QPhysicsWorld::setReportStaticKinematicCollisions(
bool newReportStaticKinematicCollisions)
1554 if (m_reportStaticKinematicCollisions == newReportStaticKinematicCollisions)
1556 m_reportStaticKinematicCollisions = newReportStaticKinematicCollisions;
1557 emit reportStaticKinematicCollisionsChanged();
1562#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