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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
96
97
98
99
100
101
102
105
106
107
108
109
110
111
112
113
114
117
118
119
120
121
122
123
124
125
126
129
130
131
132
133
134
137
138
139
140
141
142
143
144
145
146
147
150
151
152
153
154
155
156
157
158
159
160
163
164
165
166
167
168
169
170
173
174
175
176
177
178
181
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
213
214
215
216
217
218
219
220
221
222
223
224
225
228
229
230
231
232
233
234
235
236
237
238
239
240
241
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
271
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
328void QPhysicsWorld::DebugModelHolder::releaseMeshPointer()
330 if (
auto base =
static_cast<physx::PxBase *>(ptr); base)
335const QVector3D &QPhysicsWorld::DebugModelHolder::halfExtents()
const
339void QPhysicsWorld::DebugModelHolder::setHalfExtents(
const QVector3D &halfExtents)
343float QPhysicsWorld::DebugModelHolder::radius()
const
347void QPhysicsWorld::DebugModelHolder::setRadius(
float radius)
351float QPhysicsWorld::DebugModelHolder::heightScale()
const
355void QPhysicsWorld::DebugModelHolder::setHeightScale(
float heightScale)
357 data.setX(heightScale);
359float QPhysicsWorld::DebugModelHolder::halfHeight()
const
363void QPhysicsWorld::DebugModelHolder::setHalfHeight(
float halfHeight)
365 data.setY(halfHeight);
367float QPhysicsWorld::DebugModelHolder::rowScale()
const
371void QPhysicsWorld::DebugModelHolder::setRowScale(
float rowScale)
375float QPhysicsWorld::DebugModelHolder::columnScale()
const
379void QPhysicsWorld::DebugModelHolder::setColumnScale(
float columnScale)
381 data.setZ(columnScale);
383physx::PxConvexMesh *QPhysicsWorld::DebugModelHolder::getConvexMesh()
385 return static_cast<physx::PxConvexMesh *>(ptr);
387void QPhysicsWorld::DebugModelHolder::setConvexMesh(physx::PxConvexMesh *mesh)
389 ptr =
static_cast<
void *>(mesh);
391physx::PxTriangleMesh *QPhysicsWorld::DebugModelHolder::getTriangleMesh()
393 return static_cast<physx::PxTriangleMesh *>(ptr);
395void QPhysicsWorld::DebugModelHolder::setTriangleMesh(physx::PxTriangleMesh *mesh)
397 ptr =
static_cast<
void *>(mesh);
399physx::PxHeightField *QPhysicsWorld::DebugModelHolder::getHeightField()
401 return static_cast<physx::PxHeightField *>(ptr);
403void QPhysicsWorld::DebugModelHolder::setHeightField(physx::PxHeightField *hf)
405 ptr =
static_cast<physx::PxHeightField *>(hf);
419void QPhysicsWorld::registerNode(QAbstractPhysicsNode *physicsNode)
421 auto world = getWorld(physicsNode);
423 world->m_newPhysicsNodes.push_back(physicsNode);
425 worldManager.orphanNodes.push_back(physicsNode);
429void QPhysicsWorld::deregisterNode(QAbstractPhysicsNode *physicsNode)
431 for (
auto world : std::as_const(worldManager.worlds)) {
432 world->m_newPhysicsNodes.removeAll(physicsNode);
433 QMutexLocker locker(&world->m_removedPhysicsNodesMutex);
434 if (physicsNode->m_backendObject) {
435 Q_ASSERT(physicsNode->m_backendObject->frontendNode == physicsNode);
436 physicsNode->m_backendObject->frontendNode =
nullptr;
437 physicsNode->m_backendObject->isRemoved =
true;
438 physicsNode->m_backendObject =
nullptr;
440 world->m_removedPhysicsNodes.insert(physicsNode);
442 worldManager.orphanNodes.removeAll(physicsNode);
445void QPhysicsWorld::registerJoint(QPhysicsJoint *joint)
447 auto world = getWorld(joint);
449 world->m_joints.push_back(joint);
451 worldManager.orphanJoints.push_back(joint);
455void QPhysicsWorld::deregisterJoint(QPhysicsJoint *joint)
457 for (
auto world : worldManager.worlds) {
458 QMutexLocker locker(&world->m_removedPhysicsNodesMutex);
459 world->m_removedJoints.insert(joint->getPhysXBackend());
462 qsizetype idx = world->m_joints.indexOf(joint);
464 world->m_joints.swapItemsAt(idx, world->m_joints.size() - 1);
465 world->m_joints.pop_back();
468 worldManager.orphanJoints.removeAll(joint);
471void QPhysicsWorld::registerContact(QAbstractPhysicsNode *sender, QAbstractPhysicsNode *receiver,
472 const QVector<QVector3D> &positions,
473 const QVector<QVector3D> &impulses,
474 const QVector<QVector3D> &normals)
483 contact.sender = sender;
484 contact.receiver = receiver;
485 contact.positions = positions;
486 contact.impulses = impulses;
487 contact.normals = normals;
489 m_registeredContacts.push_back(contact);
492QPhysicsWorld::QPhysicsWorld(QObject *parent) : QObject(parent)
494 m_inDesignStudio = !qEnvironmentVariableIsEmpty(
"QML_PUPPET_MODE");
495 m_physx =
new QPhysXWorld;
496 m_physx->createWorld();
498 worldManager.worlds.push_back(
this);
502 m_frameAnimator =
new FrameAnimator;
503 connect(m_frameAnimator, &QQuickFrameAnimation::triggered,
this,
504 &QPhysicsWorld::simulateFrame);
507QPhysicsWorld::~QPhysicsWorld()
509 if (m_frameAnimator) {
510 m_frameAnimator->stop();
511 delete m_frameAnimator;
514 if (m_physx->scene && m_physx->isRunning && !m_frameFetched)
515 m_physx->scene->fetchResults(
true);
517 for (
auto body : std::as_const(m_physXBodies)) {
518 body->cleanup(m_physx);
521 m_physXBodies.clear();
522 m_physx->deleteWorld();
524 worldManager.worlds.removeAll(
this);
526 if (!qtPhysicsTimingsFile.isEmpty()) {
527 if (m_frameTimings.isEmpty()) {
528 qWarning() <<
"No frame timings saved.";
529 }
else if (
auto csvFile = QFile(qtPhysicsTimingsFile); csvFile.open(QIODevice::WriteOnly)) {
530 QTextStream out(&csvFile);
531 for (
int i = 1; i < m_frameTimings.size(); i++) {
532 out << i <<
"," << m_frameTimings[i] <<
'\n';
536 qWarning() <<
"Could not open timings file " << qtPhysicsTimingsFile;
541void QPhysicsWorld::classBegin() {}
543void QPhysicsWorld::componentComplete()
545 if ((!m_running && !m_inDesignStudio) || m_physicsInitialized)
550QVector3D QPhysicsWorld::gravity()
const
555bool QPhysicsWorld::running()
const
560bool QPhysicsWorld::forceDebugDraw()
const
562 return m_forceDebugDraw;
565bool QPhysicsWorld::enableCCD()
const
570float QPhysicsWorld::typicalLength()
const
572 return m_typicalLength;
575float QPhysicsWorld::typicalSpeed()
const
577 return m_typicalSpeed;
580bool QPhysicsWorld::isNodeRemoved(QAbstractPhysicsNode *object)
582 return m_removedPhysicsNodes.contains(object);
585void QPhysicsWorld::setGravity(QVector3D gravity)
587 if (m_gravity == gravity)
591 if (m_physx->scene) {
592 m_physx->scene->setGravity(QPhysicsUtils::toPhysXType(m_gravity));
594 emit gravityChanged(m_gravity);
597void QPhysicsWorld::setRunning(
bool running)
599 if (m_running == running)
603 if (!m_inDesignStudio && m_running && !m_physicsInitialized)
607 m_frameAnimator->start();
609 m_frameAnimator->stop();
611 emit runningChanged(m_running);
614void QPhysicsWorld::setForceDebugDraw(
bool forceDebugDraw)
616 if (m_forceDebugDraw == forceDebugDraw)
619 m_forceDebugDraw = forceDebugDraw;
620 if (!m_forceDebugDraw)
624 emit forceDebugDrawChanged(m_forceDebugDraw);
627QQuick3DNode *QPhysicsWorld::viewport()
const
632void QPhysicsWorld::setHasIndividualDebugDraw()
634 m_hasIndividualDebugDraw =
true;
637void QPhysicsWorld::setViewport(QQuick3DNode *viewport)
639 if (m_viewport == viewport)
642 m_viewport = viewport;
645 for (
auto material : std::as_const(m_debugMaterials))
647 m_debugMaterials.clear();
649 for (
auto &holder : m_collisionShapeDebugModels) {
650 holder.releaseMeshPointer();
653 m_collisionShapeDebugModels.clear();
655 emit viewportChanged(m_viewport);
658void QPhysicsWorld::setMinimumTimestep(
float minTimestep)
660 if (qFuzzyCompare(m_minTimestep, minTimestep))
663 if (minTimestep > m_maxTimestep) {
664 qWarning(
"Minimum timestep greater than maximum timestep, value clamped");
665 minTimestep = qMin(minTimestep, m_maxTimestep);
668 if (minTimestep < 0.f) {
669 qWarning(
"Minimum timestep less than zero, value clamped");
670 minTimestep = qMax(minTimestep, 0.f);
673 if (qFuzzyCompare(m_minTimestep, minTimestep))
676 m_minTimestep = minTimestep;
677 emit minimumTimestepChanged(m_minTimestep);
680void QPhysicsWorld::setMaximumTimestep(
float maxTimestep)
682 if (qFuzzyCompare(m_maxTimestep, maxTimestep))
685 if (maxTimestep < 0.f) {
686 qWarning(
"Maximum timestep less than zero, value clamped");
687 maxTimestep = qMax(maxTimestep, 0.f);
690 if (qFuzzyCompare(m_maxTimestep, maxTimestep))
693 m_maxTimestep = maxTimestep;
694 emit maximumTimestepChanged(maxTimestep);
697void QPhysicsWorld::setupDebugMaterials(QQuick3DNode *sceneNode)
699 if (!m_debugMaterials.isEmpty())
702 const int lineWidth = m_inDesignStudio ? 1 : 3;
705 for (
auto color : { QColorConstants::Svg::chartreuse, QColorConstants::Svg::cyan,
706 QColorConstants::Svg::lightsalmon, QColorConstants::Svg::red,
707 QColorConstants::Svg::blueviolet, QColorConstants::Svg::black }) {
708 auto debugMaterial =
new QQuick3DPrincipledMaterial();
709 debugMaterial->setLineWidth(lineWidth);
710 debugMaterial->setParentItem(sceneNode);
711 debugMaterial->setParent(sceneNode);
712 debugMaterial->setBaseColor(color);
713 debugMaterial->setLighting(QQuick3DPrincipledMaterial::NoLighting);
714 debugMaterial->setCullMode(QQuick3DMaterial::NoCulling);
715 m_debugMaterials.push_back(debugMaterial);
719void QPhysicsWorld::updateDebugDraw()
721 if (!(m_forceDebugDraw || m_hasIndividualDebugDraw)) {
723 for (
auto &holder : m_collisionShapeDebugModels) {
724 holder.releaseMeshPointer();
727 m_collisionShapeDebugModels.clear();
732 auto sceneNode = m_viewport ? m_viewport : m_scene;
734 if (sceneNode ==
nullptr)
737 setupDebugMaterials(sceneNode);
738 m_hasIndividualDebugDraw =
false;
741 QSet<QPair<QAbstractCollisionShape *, QAbstractPhysXNode *>> currentCollisionShapes;
742 currentCollisionShapes.reserve(m_collisionShapeDebugModels.size());
744 for (QAbstractPhysXNode *node : std::as_const(m_physXBodies)) {
745 if (!node->debugGeometryCapability())
748 const auto &collisionShapes = node->frontendNode->getCollisionShapesList();
749 const int materialIdx =
static_cast<
int>(node->getDebugDrawBodyType());
750 const int length = collisionShapes.length();
751 for (
int idx = 0; idx < length; idx++) {
752 const auto collisionShape = collisionShapes[idx];
754 if (!m_forceDebugDraw && !collisionShape->enableDebugDraw())
757 DebugModelHolder &holder =
758 m_collisionShapeDebugModels[std::make_pair(collisionShape, node)];
759 auto &model = holder.model;
761 currentCollisionShapes.insert(std::make_pair(collisionShape, node));
763 m_hasIndividualDebugDraw =
764 m_hasIndividualDebugDraw || collisionShape->enableDebugDraw();
768 model =
new QQuick3DModel();
769 model->setParentItem(sceneNode);
770 model->setParent(sceneNode);
771 model->setCastsShadows(
false);
772 model->setReceivesShadows(
false);
773 model->setCastsReflections(
false);
776 model->setVisible(
true);
779 auto material = m_debugMaterials[materialIdx];
780 QQmlListReference materialsRef(model,
"materials");
781 if (materialsRef.count() == 0 || materialsRef.at(0) != material) {
782 materialsRef.clear();
783 materialsRef.append(material);
789 if (qobject_cast<QCharacterController *>(node->frontendNode)) {
790 QCapsuleShape *capsuleShape = qobject_cast<QCapsuleShape *>(collisionShape);
794 const float radius = capsuleShape->diameter() * 0.5;
795 const float halfHeight = capsuleShape->height() * 0.5;
797 if (!qFuzzyCompare(radius, holder.radius())
798 || !qFuzzyCompare(halfHeight, holder.halfHeight())) {
799 auto geom = QDebugDrawHelper::generateCapsuleGeometry(radius, halfHeight);
800 geom->setParent(model);
801 model->setGeometry(geom);
802 holder.setRadius(radius);
803 holder.setHalfHeight(halfHeight);
806 model->setPosition(node->frontendNode->scenePosition());
807 model->setRotation(node->frontendNode->sceneRotation()
808 * QQuaternion::fromEulerAngles(0, 0, 90));
812 if (node->shapes.length() < length)
815 const auto physXShape = node->shapes[idx];
816 auto localPose = physXShape->getLocalPose();
818 switch (physXShape->getGeometryType()) {
819 case physx::PxGeometryType::eBOX: {
820 physx::PxBoxGeometry boxGeometry;
821 physXShape->getBoxGeometry(boxGeometry);
822 const auto &halfExtentsOld = holder.halfExtents();
823 const auto halfExtents = QPhysicsUtils::toQtType(boxGeometry.halfExtents);
824 if (!qFuzzyCompare(halfExtentsOld, halfExtents)) {
825 auto geom = QDebugDrawHelper::generateBoxGeometry(halfExtents);
826 geom->setParent(model);
827 model->setGeometry(geom);
828 holder.setHalfExtents(halfExtents);
834 case physx::PxGeometryType::eSPHERE: {
835 physx::PxSphereGeometry sphereGeometry;
836 physXShape->getSphereGeometry(sphereGeometry);
837 const float radius = holder.radius();
838 if (!qFuzzyCompare(sphereGeometry.radius, radius)) {
839 auto geom = QDebugDrawHelper::generateSphereGeometry(sphereGeometry.radius);
840 geom->setParent(model);
841 model->setGeometry(geom);
842 holder.setRadius(sphereGeometry.radius);
847 case physx::PxGeometryType::eCAPSULE: {
848 physx::PxCapsuleGeometry capsuleGeometry;
849 physXShape->getCapsuleGeometry(capsuleGeometry);
850 const float radius = holder.radius();
851 const float halfHeight = holder.halfHeight();
853 if (!qFuzzyCompare(capsuleGeometry.radius, radius)
854 || !qFuzzyCompare(capsuleGeometry.halfHeight, halfHeight)) {
855 auto geom = QDebugDrawHelper::generateCapsuleGeometry(
856 capsuleGeometry.radius, capsuleGeometry.halfHeight);
857 geom->setParent(model);
858 model->setGeometry(geom);
859 holder.setRadius(capsuleGeometry.radius);
860 holder.setHalfHeight(capsuleGeometry.halfHeight);
865 case physx::PxGeometryType::ePLANE:{
866 physx::PxPlaneGeometry planeGeometry;
867 physXShape->getPlaneGeometry(planeGeometry);
869 const QQuaternion rotation =
870 QPhysicsUtils::kMinus90YawRotation * QPhysicsUtils::toQtType(localPose.q);
871 localPose = physx::PxTransform(localPose.p, QPhysicsUtils::toPhysXType(rotation));
873 if (model->geometry() ==
nullptr) {
874 auto geom = QDebugDrawHelper::generatePlaneGeometry();
875 geom->setParent(model);
876 model->setGeometry(geom);
884 case physx::PxGeometryType::eHEIGHTFIELD: {
885 physx::PxHeightFieldGeometry heightFieldGeometry;
886 bool success = physXShape->getHeightFieldGeometry(heightFieldGeometry);
888 const float heightScale = holder.heightScale();
889 const float rowScale = holder.rowScale();
890 const float columnScale = holder.columnScale();
892 if (
auto heightField = holder.getHeightField();
893 heightField && heightField != heightFieldGeometry.heightField) {
894 heightField->release();
895 holder.setHeightField(
nullptr);
898 if (!qFuzzyCompare(heightFieldGeometry.heightScale, heightScale)
899 || !qFuzzyCompare(heightFieldGeometry.rowScale, rowScale)
900 || !qFuzzyCompare(heightFieldGeometry.columnScale, columnScale)
901 || !holder.getHeightField()) {
902 if (!holder.getHeightField()) {
903 heightFieldGeometry.heightField->acquireReference();
904 holder.setHeightField(heightFieldGeometry.heightField);
906 auto geom = QDebugDrawHelper::generateHeightFieldGeometry(
907 heightFieldGeometry.heightField, heightFieldGeometry.heightScale,
908 heightFieldGeometry.rowScale, heightFieldGeometry.columnScale);
909 geom->setParent(model);
910 model->setGeometry(geom);
911 holder.setHeightScale(heightFieldGeometry.heightScale);
912 holder.setRowScale(heightFieldGeometry.rowScale);
913 holder.setColumnScale(heightFieldGeometry.columnScale);
918 case physx::PxGeometryType::eCONVEXMESH: {
919 physx::PxConvexMeshGeometry convexMeshGeometry;
920 const bool success = physXShape->getConvexMeshGeometry(convexMeshGeometry);
922 const auto rotation = convexMeshGeometry.scale.rotation * localPose.q;
923 localPose = physx::PxTransform(localPose.p, rotation);
924 model->setScale(QPhysicsUtils::toQtType(convexMeshGeometry.scale.scale));
926 if (
auto convexMesh = holder.getConvexMesh();
927 convexMesh && convexMesh != convexMeshGeometry.convexMesh) {
928 convexMesh->release();
929 holder.setConvexMesh(
nullptr);
932 if (!model->geometry() || !holder.getConvexMesh()) {
933 if (!holder.getConvexMesh()) {
934 convexMeshGeometry.convexMesh->acquireReference();
935 holder.setConvexMesh(convexMeshGeometry.convexMesh);
937 auto geom = QDebugDrawHelper::generateConvexMeshGeometry(
938 convexMeshGeometry.convexMesh);
939 geom->setParent(model);
940 model->setGeometry(geom);
945 case physx::PxGeometryType::eTRIANGLEMESH: {
946 physx::PxTriangleMeshGeometry triangleMeshGeometry;
947 const bool success = physXShape->getTriangleMeshGeometry(triangleMeshGeometry);
949 const auto rotation = triangleMeshGeometry.scale.rotation * localPose.q;
950 localPose = physx::PxTransform(localPose.p, rotation);
951 model->setScale(QPhysicsUtils::toQtType(triangleMeshGeometry.scale.scale));
953 if (
auto triangleMesh = holder.getTriangleMesh();
954 triangleMesh && triangleMesh != triangleMeshGeometry.triangleMesh) {
955 triangleMesh->release();
956 holder.setTriangleMesh(
nullptr);
959 if (!model->geometry() || !holder.getTriangleMesh()) {
960 if (!holder.getTriangleMesh()) {
961 triangleMeshGeometry.triangleMesh->acquireReference();
962 holder.setTriangleMesh(triangleMeshGeometry.triangleMesh);
964 auto geom = QDebugDrawHelper::generateTriangleMeshGeometry(
965 triangleMeshGeometry.triangleMesh);
966 geom->setParent(model);
967 model->setGeometry(geom);
972 case physx::PxGeometryType::eINVALID:
973 case physx::PxGeometryType::eGEOMETRY_COUNT:
978 auto globalPose = node->getGlobalPose();
979 auto finalPose = globalPose.transform(localPose);
981 model->setRotation(QPhysicsUtils::toQtType(finalPose.q));
982 model->setPosition(QPhysicsUtils::toQtType(finalPose.p));
987 m_collisionShapeDebugModels.removeIf(
988 [&](QHash<QPair<QAbstractCollisionShape *, QAbstractPhysXNode *>,
989 DebugModelHolder>::iterator it) {
990 if (!currentCollisionShapes.contains(it.key())) {
991 auto holder = it.value();
992 holder.releaseMeshPointer();
1003 if (
auto shape = qobject_cast<QAbstractPhysicsNode *>(node)) {
1004 nodes.push_back(shape);
1008 auto childItems = node->childItems();
1009 for (QQuick3DObject *child : std::as_const(childItems))
1010 collectPhysicsNodes(child, nodes);
1013void QPhysicsWorld::updateDebugDrawDesignStudio()
1016 auto sceneNode = m_viewport ? m_viewport : m_scene;
1018 if (sceneNode ==
nullptr)
1021 setupDebugMaterials(sceneNode);
1024 QSet<QPair<QAbstractCollisionShape *, QAbstractPhysicsNode *>> currentCollisionShapes;
1025 currentCollisionShapes.reserve(m_collisionShapeDebugModels.size());
1027 QList<QAbstractPhysicsNode *> activePhysicsNodes;
1028 activePhysicsNodes.reserve(m_collisionShapeDebugModels.size());
1029 collectPhysicsNodes(m_scene, activePhysicsNodes);
1031 for (QAbstractPhysicsNode *node : std::as_const(activePhysicsNodes)) {
1033 const auto &collisionShapes = node->getCollisionShapesList();
1034 const int materialIdx = 0;
1035 const int length = collisionShapes.length();
1037 const bool isCharacterController = qobject_cast<QCharacterController *>(node) !=
nullptr;
1039 for (
int idx = 0; idx < length; idx++) {
1040 QAbstractCollisionShape *collisionShape = collisionShapes[idx];
1041 DebugModelHolder &holder =
1042 m_DesignStudioDebugModels[std::make_pair(collisionShape, node)];
1043 auto &model = holder.model;
1045 currentCollisionShapes.insert(std::make_pair(collisionShape, node));
1047 m_hasIndividualDebugDraw =
1048 m_hasIndividualDebugDraw || collisionShape->enableDebugDraw();
1055 model =
new QQuick3DModel();
1056 model->setParentItem(sceneNode);
1057 model->setParent(sceneNode);
1058 model->setCastsShadows(
false);
1059 model->setReceivesShadows(
false);
1060 model->setCastsReflections(
false);
1063 const bool hasGeometry = holder.geometry !=
nullptr;
1064 QVector3D scenePosition = collisionShape->scenePosition();
1065 QQuaternion sceneRotation = collisionShape->sceneRotation();
1066 QQuick3DGeometry *newGeometry =
nullptr;
1068 if (isCharacterController)
1069 sceneRotation = sceneRotation * QQuaternion::fromEulerAngles(QVector3D(0, 0, 90));
1072 auto material = m_debugMaterials[materialIdx];
1073 QQmlListReference materialsRef(model,
"materials");
1074 if (materialsRef.count() == 0 || materialsRef.at(0) != material) {
1075 materialsRef.clear();
1076 materialsRef.append(material);
1080 if (
auto shape = qobject_cast<QBoxShape *>(collisionShape)) {
1081 const auto &halfExtentsOld = holder.halfExtents();
1082 const auto halfExtents = shape->sceneScale() * shape->extents() * 0.5f;
1083 if (!qFuzzyCompare(halfExtentsOld, halfExtents) || !hasGeometry) {
1084 newGeometry = QDebugDrawHelper::generateBoxGeometry(halfExtents);
1085 holder.setHalfExtents(halfExtents);
1087 }
else if (
auto shape = qobject_cast<QSphereShape *>(collisionShape)) {
1088 const float radiusOld = holder.radius();
1089 const float radius = shape->sceneScale().x() * shape->diameter() * 0.5f;
1090 if (!qFuzzyCompare(radiusOld, radius) || !hasGeometry) {
1091 newGeometry = QDebugDrawHelper::generateSphereGeometry(radius);
1092 holder.setRadius(radius);
1094 }
else if (
auto shape = qobject_cast<QCapsuleShape *>(collisionShape)) {
1095 const float radiusOld = holder.radius();
1096 const float halfHeightOld = holder.halfHeight();
1097 const float radius = shape->sceneScale().y() * shape->diameter() * 0.5f;
1098 const float halfHeight = shape->sceneScale().x() * shape->height() * 0.5f;
1100 if ((!qFuzzyCompare(radiusOld, radius) || !qFuzzyCompare(halfHeightOld, halfHeight))
1102 newGeometry = QDebugDrawHelper::generateCapsuleGeometry(radius, halfHeight);
1103 holder.setRadius(radius);
1104 holder.setHalfHeight(halfHeight);
1106 }
else if (qobject_cast<QPlaneShape *>(collisionShape)) {
1108 newGeometry = QDebugDrawHelper::generatePlaneGeometry();
1109 }
else if (
auto shape = qobject_cast<QHeightFieldShape *>(collisionShape)) {
1110 physx::PxHeightFieldGeometry *heightFieldGeometry =
1111 static_cast<physx::PxHeightFieldGeometry *>(shape->getPhysXGeometry());
1112 const float heightScale = holder.heightScale();
1113 const float rowScale = holder.rowScale();
1114 const float columnScale = holder.columnScale();
1115 scenePosition += shape->hfOffset();
1116 if (!heightFieldGeometry) {
1117 qWarning() <<
"Could not get height field";
1118 }
else if (!qFuzzyCompare(heightFieldGeometry->heightScale, heightScale)
1119 || !qFuzzyCompare(heightFieldGeometry->rowScale, rowScale)
1120 || !qFuzzyCompare(heightFieldGeometry->columnScale, columnScale)
1122 newGeometry = QDebugDrawHelper::generateHeightFieldGeometry(
1123 heightFieldGeometry->heightField, heightFieldGeometry->heightScale,
1124 heightFieldGeometry->rowScale, heightFieldGeometry->columnScale);
1125 holder.setHeightScale(heightFieldGeometry->heightScale);
1126 holder.setRowScale(heightFieldGeometry->rowScale);
1127 holder.setColumnScale(heightFieldGeometry->columnScale);
1129 }
else if (
auto shape = qobject_cast<QConvexMeshShape *>(collisionShape)) {
1130 auto convexMeshGeometry =
1131 static_cast<physx::PxConvexMeshGeometry *>(shape->getPhysXGeometry());
1132 if (!convexMeshGeometry) {
1133 qWarning() <<
"Could not get convex mesh";
1135 model->setScale(QPhysicsUtils::toQtType(convexMeshGeometry->scale.scale));
1138 newGeometry = QDebugDrawHelper::generateConvexMeshGeometry(
1139 convexMeshGeometry->convexMesh);
1142 }
else if (
auto shape = qobject_cast<QTriangleMeshShape *>(collisionShape)) {
1143 physx::PxTriangleMeshGeometry *triangleMeshGeometry =
1144 static_cast<physx::PxTriangleMeshGeometry *>(shape->getPhysXGeometry());
1145 if (!triangleMeshGeometry) {
1146 qWarning() <<
"Could not get triangle mesh";
1148 model->setScale(QPhysicsUtils::toQtType(triangleMeshGeometry->scale.scale));
1151 newGeometry = QDebugDrawHelper::generateTriangleMeshGeometry(
1152 triangleMeshGeometry->triangleMesh);
1158 delete holder.geometry;
1159 holder.geometry = newGeometry;
1162 model->setGeometry(holder.geometry);
1163 model->setVisible(
true);
1165 model->setRotation(sceneRotation);
1166 model->setPosition(scenePosition);
1171 m_DesignStudioDebugModels.removeIf(
1172 [&](QHash<QPair<QAbstractCollisionShape *, QAbstractPhysicsNode *>,
1173 DebugModelHolder>::iterator it) {
1174 if (!currentCollisionShapes.contains(it.key())) {
1175 auto holder = it.value();
1176 holder.releaseMeshPointer();
1178 delete holder.geometry;
1179 delete holder.model;
1187void QPhysicsWorld::disableDebugDraw()
1189 m_hasIndividualDebugDraw =
false;
1191 for (QAbstractPhysXNode *body : std::as_const(m_physXBodies)) {
1192 const auto &collisionShapes = body->frontendNode->getCollisionShapesList();
1193 const int length = collisionShapes.length();
1194 for (
int idx = 0; idx < length; idx++) {
1195 const auto collisionShape = collisionShapes[idx];
1196 if (collisionShape->enableDebugDraw()) {
1197 m_hasIndividualDebugDraw =
true;
1204void QPhysicsWorld::setEnableCCD(
bool enableCCD)
1206 if (m_enableCCD == enableCCD)
1209 if (m_physicsInitialized) {
1211 <<
"Warning: Changing 'enableCCD' after physics is initialized will have no effect";
1215 m_enableCCD = enableCCD;
1216 emit enableCCDChanged(m_enableCCD);
1219void QPhysicsWorld::setTypicalLength(
float typicalLength)
1221 if (qFuzzyCompare(typicalLength, m_typicalLength))
1224 if (typicalLength <= 0.f) {
1225 qWarning() <<
"Warning: 'typicalLength' value less than zero, ignored";
1229 if (m_physicsInitialized) {
1230 qWarning() <<
"Warning: Changing 'typicalLength' after physics is initialized will have "
1235 m_typicalLength = typicalLength;
1237 emit typicalLengthChanged(typicalLength);
1240void QPhysicsWorld::setTypicalSpeed(
float typicalSpeed)
1242 if (qFuzzyCompare(typicalSpeed, m_typicalSpeed))
1245 if (m_physicsInitialized) {
1246 qWarning() <<
"Warning: Changing 'typicalSpeed' after physics is initialized will have "
1251 m_typicalSpeed = typicalSpeed;
1253 emit typicalSpeedChanged(typicalSpeed);
1256float QPhysicsWorld::defaultDensity()
const
1258 return m_defaultDensity;
1261float QPhysicsWorld::minimumTimestep()
const
1263 return m_minTimestep;
1266float QPhysicsWorld::maximumTimestep()
const
1268 return m_maxTimestep;
1271void QPhysicsWorld::setDefaultDensity(
float defaultDensity)
1273 if (qFuzzyCompare(m_defaultDensity, defaultDensity))
1275 m_defaultDensity = defaultDensity;
1278 for (QAbstractPhysXNode *body : std::as_const(m_physXBodies))
1279 body->updateDefaultDensity(m_defaultDensity);
1281 emit defaultDensityChanged(defaultDensity);
1286void QPhysicsWorld::cleanupRemovedNodes()
1288 m_physXBodies.removeIf([
this](QAbstractPhysXNode *body) {
1289 return body->cleanupIfRemoved(m_physx);
1291 m_removedPhysicsNodes.clear();
1294void QPhysicsWorld::cleanupRemovedJoints()
1296 for (physx::PxJoint *joint : m_removedJoints) {
1300 m_removedJoints.clear();
1303void QPhysicsWorld::initPhysics()
1305 Q_ASSERT(!m_physicsInitialized);
1307 const unsigned int numThreads = m_numThreads >= 0 ? m_numThreads : qMax(0, QThread::idealThreadCount());
1308 m_physx->createScene(m_typicalLength, m_typicalSpeed, m_gravity,
this, numThreads);
1309 m_frameAnimator->start();
1310 m_physicsInitialized =
true;
1313void QPhysicsWorld::simulateFrame()
1315 constexpr double MILLIONTH = 0.000001;
1316 constexpr double THOUSANDTH = 0.001;
1318 if (m_inDesignStudio) {
1319 frameFinishedDesignStudio();
1323 if (!m_physx->isRunning) {
1325 m_physx->isRunning =
true;
1326 const double minTimestepSecs = m_minTimestep * 0.001;
1327 m_physx->scene->simulate(minTimestepSecs);
1328 m_currTimeStep = minTimestepSecs;
1333 if (!m_frameFetched && !m_physx->scene->checkResults()) {
1338 if (!m_frameFetched) {
1339 m_physx->scene->fetchResults(
true);
1340 frameFinished(m_currTimeStep);
1341 m_frameFetched =
true;
1342 if (Q_UNLIKELY(!qtPhysicsTimingsFile.isEmpty())) {
1343 const double deltaMS = m_timer.nsecsElapsed() * MILLIONTH;
1344 m_frameTimings.append(deltaMS);
1349 const double deltaMS = m_timer.nsecsElapsed() * MILLIONTH;
1350 if (deltaMS < m_minTimestep)
1352 const double deltaSecs = qMin<
double>(deltaMS, m_maxTimestep) * THOUSANDTH;
1354 m_physx->scene->simulate(deltaSecs);
1355 m_frameFetched =
false;
1356 m_currTimeStep = deltaSecs;
1359void QPhysicsWorld::frameFinished(
float deltaTime)
1362 matchOrphanJoints();
1363 emitContactCallbacks();
1364 cleanupRemovedNodes();
1365 cleanupRemovedJoints();
1367 for (
auto *node : std::as_const(m_newPhysicsNodes)) {
1368 auto *body = node->createPhysXBackend();
1369 body->init(
this, m_physx);
1370 m_physXBodies.push_back(body);
1372 m_newPhysicsNodes.clear();
1374 QHash<QQuick3DNode *, QMatrix4x4> transformCache;
1377 for (
auto *physXBody : std::as_const(m_physXBodies)) {
1378 physXBody->markDirtyShapes();
1379 physXBody->rebuildDirtyShapes(
this, m_physx);
1380 physXBody->updateFilters();
1383 physXBody->sync(deltaTime, transformCache);
1386 for (QPhysicsJoint *joint : std::as_const(m_joints)) {
1387 joint->updatePhysXBackend();
1391 emit frameDone(deltaTime * 1000);
1394void QPhysicsWorld::frameFinishedDesignStudio()
1398 matchOrphanJoints();
1399 emitContactCallbacks();
1400 cleanupRemovedNodes();
1402 m_newPhysicsNodes.clear();
1404 updateDebugDrawDesignStudio();
1407QPhysicsWorld *QPhysicsWorld::getWorld(QQuick3DNode *node)
1409 for (QPhysicsWorld *world : std::as_const(worldManager.worlds)) {
1410 if (!world->m_scene) {
1414 QQuick3DNode *nodeCurr = node;
1417 if (nodeCurr == world->m_scene)
1420 while (nodeCurr->parentNode()) {
1421 nodeCurr = nodeCurr->parentNode();
1422 if (nodeCurr == world->m_scene)
1430QPhysicsWorld *QPhysicsWorld::getWorld(QPhysicsJoint *joint)
1432 for (QPhysicsWorld *world : worldManager.worlds) {
1433 if (!world->m_scene) {
1437 QObject *nodeCurr = joint;
1439 while (nodeCurr->parent()) {
1440 nodeCurr = nodeCurr->parent();
1441 if (nodeCurr == world->m_scene) {
1443 }
else if (
auto view3d = qobject_cast<QQuick3DViewport *>(nodeCurr);
1444 view3d && view3d->scene() == world->m_scene) {
1454void QPhysicsWorld::matchOrphanNodes()
1457 if (worldManager.orphanNodes.isEmpty())
1460 qsizetype numNodes = worldManager.orphanNodes.length();
1463 while (idx < numNodes) {
1464 auto node = worldManager.orphanNodes[idx];
1465 auto world = getWorld(node);
1466 if (world ==
this) {
1467 world->m_newPhysicsNodes.push_back(node);
1469 worldManager.orphanNodes.swapItemsAt(idx, numNodes - 1);
1470 worldManager.orphanNodes.pop_back();
1478void QPhysicsWorld::matchOrphanJoints()
1480 if (worldManager.orphanJoints.isEmpty())
1483 qsizetype numNodes = worldManager.orphanJoints.length();
1486 while (idx < numNodes) {
1487 auto node = worldManager.orphanJoints[idx];
1488 auto world = getWorld(node);
1489 if (world ==
this) {
1490 world->m_joints.push_back(node);
1492 worldManager.orphanJoints.swapItemsAt(idx, numNodes - 1);
1493 worldManager.orphanJoints.pop_back();
1501void QPhysicsWorld::findPhysicsNodes()
1506 if (m_scene ==
nullptr)
1510 QList<QQuick3DObject *> children = m_scene->childItems();
1511 while (!children.empty()) {
1512 auto child = children.takeFirst();
1513 if (
auto converted = qobject_cast<QAbstractPhysicsNode *>(child); converted !=
nullptr) {
1515 if (converted->m_backendObject !=
nullptr) {
1516 qWarning() <<
"Warning: physics node already associated with a backend node.";
1520 m_newPhysicsNodes.push_back(converted);
1521 worldManager.orphanNodes.removeAll(converted);
1523 children.append(child->childItems());
1527void QPhysicsWorld::emitContactCallbacks()
1529 for (
const QPhysicsWorld::BodyContact &contact : std::as_const(m_registeredContacts)) {
1530 if (m_removedPhysicsNodes.contains(contact.sender)
1531 || m_removedPhysicsNodes.contains(contact.receiver))
1533 contact.receiver->registerContact(contact.sender, contact.positions, contact.impulses,
1537 m_registeredContacts.clear();
1540physx::PxPhysics *QPhysicsWorld::getPhysics()
1542 return StaticPhysXObjects::getReference().physics;
1545physx::PxCooking *QPhysicsWorld::getCooking()
1547 return StaticPhysXObjects::getReference().cooking;
1550physx::PxControllerManager *QPhysicsWorld::controllerManager()
1552 if (m_physx->scene && !m_physx->controllerManager) {
1553 m_physx->controllerManager = PxCreateControllerManager(*m_physx->scene);
1554 qCDebug(lcQuick3dPhysics) <<
"Created controller manager" << m_physx->controllerManager;
1556 return m_physx->controllerManager;
1559QQuick3DNode *QPhysicsWorld::scene()
const
1564void QPhysicsWorld::setScene(QQuick3DNode *newScene)
1566 if (m_scene == newScene)
1572 for (
auto body : std::as_const(m_physXBodies)) {
1573 deregisterNode(body->frontendNode);
1577 bool sceneOK =
true;
1578 for (QPhysicsWorld *world : std::as_const(worldManager.worlds)) {
1579 if (world !=
this && world->scene() == newScene) {
1581 qWarning() <<
"Warning: scene already associated with physics world";
1587 emit sceneChanged();
1590int QPhysicsWorld::numThreads()
const
1592 return m_numThreads;
1595void QPhysicsWorld::setNumThreads(
int newNumThreads)
1597 if (m_numThreads == newNumThreads)
1599 m_numThreads = newNumThreads;
1600 emit numThreadsChanged();
1603bool QPhysicsWorld::reportKinematicKinematicCollisions()
const
1605 return m_reportKinematicKinematicCollisions;
1608void QPhysicsWorld::setReportKinematicKinematicCollisions(
1609 bool newReportKinematicKinematicCollisions)
1611 if (m_reportKinematicKinematicCollisions == newReportKinematicKinematicCollisions)
1613 m_reportKinematicKinematicCollisions = newReportKinematicKinematicCollisions;
1614 emit reportKinematicKinematicCollisionsChanged();
1617bool QPhysicsWorld::reportStaticKinematicCollisions()
const
1619 return m_reportStaticKinematicCollisions;
1622void QPhysicsWorld::setReportStaticKinematicCollisions(
bool newReportStaticKinematicCollisions)
1624 if (m_reportStaticKinematicCollisions == newReportStaticKinematicCollisions)
1626 m_reportStaticKinematicCollisions = newReportStaticKinematicCollisions;
1627 emit reportStaticKinematicCollisionsChanged();
1630QPhysicsWorld::QueryStructure QPhysicsWorld::staticQueryStructure()
const
1632 return m_staticQueryStructure;
1635void QPhysicsWorld::setStaticQueryStructure(QueryStructure newStaticQueryStructure)
1637 if (m_staticQueryStructure == newStaticQueryStructure)
1640 if (newStaticQueryStructure == QueryStructure::NoStructure) {
1642 <<
"Warning: The QueryStructure::NoStructure is not supported for staticQueryStructure. Available options: [StaticTree, DynamicTree]";
1646 if (m_physicsInitialized) {
1648 <<
"Warning: Changing 'staticQueryStructure' after physics is initialized will have no effect";
1652 m_staticQueryStructure = newStaticQueryStructure;
1653 emit staticQueryStructureChanged();
1656QPhysicsWorld::QueryStructure QPhysicsWorld::dynamicQueryStructure()
const
1658 return m_dynamicQueryStructure;
1661void QPhysicsWorld::setDynamicQueryStructure(QueryStructure newDynamicQueryStructure)
1663 if (m_dynamicQueryStructure == newDynamicQueryStructure)
1666 if (m_physicsInitialized) {
1668 <<
"Warning: Changing 'dynamicQueryStructure' after physics is initialized will have no effect";
1672 m_dynamicQueryStructure = newDynamicQueryStructure;
1673 emit dynamicQueryStructureChanged();
1678#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