Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qphysicsworld.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
6
7#include "physxnode/qabstractphysxnode_p.h"
8#include "physxnode/qphysxworld_p.h"
13#include "qboxshape_p.h"
14#include "qsphereshape_p.h"
19#include "qplaneshape_p.h"
21
22#include "PxPhysicsAPI.h"
23#include "cooking/PxCooking.h"
24
25#include <QtQuick/private/qquickframeanimation_p.h>
26#include <QtQuick3D/private/qquick3dobject_p.h>
27#include <QtQuick3D/private/qquick3dnode_p.h>
28#include <QtQuick3D/private/qquick3dmodel_p.h>
29#include <QtQuick3D/private/qquick3dprincipledmaterial_p.h>
30#include <QtQuick3DUtils/private/qssgutils_p.h>
31
32#include <QtGui/qquaternion.h>
33
34#include <QtEnvironmentVariables>
35
36#define PHYSX_ENABLE_PVD 0
37
39
40/*!
41 \qmltype PhysicsWorld
42 \inqmlmodule QtQuick3D.Physics
43 \since 6.4
44 \brief Controls the physics simulation.
45
46 The PhysicsWorld type controls the physics simulation. This node is used to create an instance of the physics world as well
47 as define its properties. There can only be one physics world. All collision nodes in the qml
48 will get added automatically to the physics world.
49*/
50
51/*!
52 \qmlproperty vector3d PhysicsWorld::gravity
53 This property defines the gravity vector of the physics world.
54 The default value is \c (0, -981, 0). Set the value to \c{Qt.vector3d(0, -9.81, 0)} if your
55 unit of measurement is meters and you are simulating Earth gravity.
56*/
57
58/*!
59 \qmlproperty bool PhysicsWorld::running
60 This property starts or stops the physical simulation. The default value is \c true.
61*/
62
63/*!
64 \qmlproperty bool PhysicsWorld::forceDebugDraw
65 This property enables debug drawing of all active shapes in the physics world. The default value
66 is \c false.
67*/
68
69/*!
70 \qmlproperty bool PhysicsWorld::enableCCD
71 This property enables continuous collision detection. This will reduce the risk of bodies going
72 through other bodies at high velocities (also known as tunnelling). The default value is \c
73 false.
74
75 \warning Using trigger bodies with CCD enabled is not supported and can result in missing or
76 false trigger reports.
77*/
78
79/*!
80 \qmlproperty real PhysicsWorld::typicalLength
81 This property defines the approximate size of objects in the simulation. This is used to
82 estimate certain length-related tolerances. Objects much smaller or much larger than this
83 size may not behave properly. The default value is \c 100.
84
85 Range: \c{[0, inf]}
86*/
87
88/*!
89 \qmlproperty real PhysicsWorld::typicalSpeed
90 This property defines the typical magnitude of velocities of objects in simulation. This is used
91 to estimate whether a contact should be treated as bouncing or resting based on its impact
92 velocity, and a kinetic energy threshold below which the simulation may put objects to sleep.
93
94 For normal physical environments, a good choice is the approximate speed of an object falling
95 under gravity for one second. The default value is \c 1000.
96
97 Range: \c{[0, inf]}
98*/
99
100/*!
101 \qmlproperty real PhysicsWorld::defaultDensity
102 This property defines the default density of dynamic objects, measured in kilograms per cubic
103 unit. This is equal to the weight of a cube with side \c 1.
104
105 The default value is \c 0.001, corresponding to 1 g/cm³: the density of water. If your unit of
106 measurement is meters, a good value would be \c 1000. Note that only positive values are
107 allowed.
108
109 Range: \c{(0, inf]}
110*/
111
112/*!
113 \qmlproperty Node PhysicsWorld::viewport
114 This property defines the viewport where debug components will be drawn if \l{forceDebugDraw}
115 is enabled. If unset the \l{scene} node will be used.
116
117 \sa forceDebugDraw, scene
118*/
119
120/*!
121 \qmlproperty real PhysicsWorld::minimumTimestep
122 This property defines the minimum simulation timestep in milliseconds. The default value is
123 \c{1}.
124
125 Range: \c{[0, maximumTimestep]}
126
127 \note The simulation timestep works in lockstep with the rendering,
128 meaning a new simulation frame will only be started after a rendered frame
129 has completed. This means that at most one simulation frame will run per
130 rendered frame.
131*/
132
133/*!
134 \qmlproperty real PhysicsWorld::maximumTimestep
135 This property defines the maximum simulation timestep in milliseconds. The default value is
136 \c{33.333}.
137
138 Range: \c{[0, inf]}
139
140 \note The simulation timestep works in lockstep with the rendering,
141 meaning a new simulation frame will only be started after a rendered frame
142 has completed. This means that at most one simulation frame will run per
143 rendered frame.
144*/
145
146/*!
147 \qmlproperty Node PhysicsWorld::scene
148
149 This property defines the top-most Node that contains all the nodes of the physical
150 simulation. All physics objects that are an ancestor of this node will be seen as part of this
151 PhysicsWorld.
152
153 \note Using the same scene node for several PhysicsWorld is unsupported.
154*/
155
156/*!
157 \qmlsignal PhysicsWorld::frameDone(float timestep)
158 \since 6.5
159
160 This signal is emitted when the physical simulation is done simulating a frame. The \a timestep
161 parameter is how long in milliseconds the timestep was in the simulation.
162*/
163
164/*!
165 \qmlproperty int PhysicsWorld::numThreads
166 \since 6.7
167
168 This property defines the number of threads used for the physical simulation. This is how the
169 range of values are interpreted:
170
171 \table
172 \header
173 \li Value
174 \li Range
175 \li Description
176 \row
177 \li Negative
178 \li \c{[-inf, -1]}
179 \li Automatic thread count. The application will try to query the number of threads from the
180 system.
181 \row
182 \li Zero
183 \li \c{{0}}
184 \li No threading, simulation will run sequentially.
185 \row
186 \li Positive
187 \li \c{[1, inf]}
188 \li Specific thread count.
189 \endtable
190
191 The default value is \c{-1}, meaning automatic thread count.
192
193 \note Once the scene has started running it is not possible to change the number of threads.
194*/
195
196/*!
197 \qmlproperty bool PhysicsWorld::reportKinematicKinematicCollisions
198 \since 6.7
199
200 This property controls if collisions between pairs of \e{kinematic} dynamic rigid bodies will
201 trigger a contact report.
202
203 The default value is \c{false}.
204
205 \note Once the scene has started running it is not possible to change this setting.
206 \sa PhysicsWorld::reportStaticKinematicCollisions
207 \sa DynamicRigidBody
208 \sa PhysicsNode::bodyContact
209*/
210
211/*!
212 \qmlproperty bool PhysicsWorld::reportStaticKinematicCollisions
213 \since 6.7
214
215 This property controls if collisions between a static rigid body and a \e{kinematic} dynamic
216 rigid body will trigger a contact report.
217
218 The default value is \c{false}.
219
220 \note Once the scene has started running it is not possible to change this setting.
221 \sa PhysicsWorld::reportKinematicKinematicCollisions
222 \sa StaticRigidBody
223 \sa DynamicRigidBody
224 \sa PhysicsNode::bodyContact
225*/
226
227Q_LOGGING_CATEGORY(lcQuick3dPhysics, "qt.quick3d.physics");
228
229// Setting QT_PHYSICS_TIMINGS_FILE to a filepath will generate a csv file with frame timings.
230// Note that if running several PhysicsWorld's the last one to be destructed will overwrite
231// the output file.
232//
233// To view it in gnuplot, run these two commands:
234//
235// set datafile separator ','
236// plot '<QT_PHYSICS_TIMINGS_FILE>' using 1:2 with lines
237//
238// Security note: This file is trusted since it is opened as write-only
239static const QString qtPhysicsTimingsFile = qEnvironmentVariable("QT_PHYSICS_TIMINGS_FILE");
240
241/////////////////////////////////////////////////////////////////////////////
242
244{
246public:
248 {
249 // Needed to start the frame animation
250 classBegin();
252 }
253};
254
255/////////////////////////////////////////////////////////////////////////////
256
257void QPhysicsWorld::DebugModelHolder::releaseMeshPointer()
258{
259 if (auto base = static_cast<physx::PxBase *>(ptr); base)
260 base->release();
261 ptr = nullptr;
262}
263
264const QVector3D &QPhysicsWorld::DebugModelHolder::halfExtents() const
265{
266 return data;
267}
268void QPhysicsWorld::DebugModelHolder::setHalfExtents(const QVector3D &halfExtents)
269{
270 data = halfExtents;
271}
272float QPhysicsWorld::DebugModelHolder::radius() const
273{
274 return data.x();
275}
276void QPhysicsWorld::DebugModelHolder::setRadius(float radius)
277{
278 data.setX(radius);
279}
280float QPhysicsWorld::DebugModelHolder::heightScale() const
281{
282 return data.x();
283}
284void QPhysicsWorld::DebugModelHolder::setHeightScale(float heightScale)
285{
286 data.setX(heightScale);
287}
288float QPhysicsWorld::DebugModelHolder::halfHeight() const
289{
290 return data.y();
291}
292void QPhysicsWorld::DebugModelHolder::setHalfHeight(float halfHeight)
293{
294 data.setY(halfHeight);
295}
296float QPhysicsWorld::DebugModelHolder::rowScale() const
297{
298 return data.y();
299}
300void QPhysicsWorld::DebugModelHolder::setRowScale(float rowScale)
301{
302 data.setY(rowScale);
303}
304float QPhysicsWorld::DebugModelHolder::columnScale() const
305{
306 return data.z();
307}
308void QPhysicsWorld::DebugModelHolder::setColumnScale(float columnScale)
309{
310 data.setZ(columnScale);
311}
312physx::PxConvexMesh *QPhysicsWorld::DebugModelHolder::getConvexMesh()
313{
314 return static_cast<physx::PxConvexMesh *>(ptr);
315}
316void QPhysicsWorld::DebugModelHolder::setConvexMesh(physx::PxConvexMesh *mesh)
317{
318 ptr = static_cast<void *>(mesh);
319}
320physx::PxTriangleMesh *QPhysicsWorld::DebugModelHolder::getTriangleMesh()
321{
322 return static_cast<physx::PxTriangleMesh *>(ptr);
323}
324void QPhysicsWorld::DebugModelHolder::setTriangleMesh(physx::PxTriangleMesh *mesh)
325{
326 ptr = static_cast<void *>(mesh);
327}
328physx::PxHeightField *QPhysicsWorld::DebugModelHolder::getHeightField()
329{
330 return static_cast<physx::PxHeightField *>(ptr);
331}
332void QPhysicsWorld::DebugModelHolder::setHeightField(physx::PxHeightField *hf)
333{
334 ptr = static_cast<physx::PxHeightField *>(hf);
335}
336
337/////////////////////////////////////////////////////////////////////////////
338
344
345static QWorldManager worldManager = QWorldManager {};
346
347void QPhysicsWorld::registerNode(QAbstractPhysicsNode *physicsNode)
348{
349 auto world = getWorld(physicsNode);
350 if (world) {
351 world->m_newPhysicsNodes.push_back(physicsNode);
352 } else {
353 worldManager.orphanNodes.push_back(physicsNode);
354 }
355}
356
357void QPhysicsWorld::deregisterNode(QAbstractPhysicsNode *physicsNode)
358{
359 for (auto world : worldManager.worlds) {
360 world->m_newPhysicsNodes.removeAll(physicsNode);
361 QMutexLocker locker(&world->m_removedPhysicsNodesMutex);
362 if (physicsNode->m_backendObject) {
363 Q_ASSERT(physicsNode->m_backendObject->frontendNode == physicsNode);
364 physicsNode->m_backendObject->frontendNode = nullptr;
365 physicsNode->m_backendObject->isRemoved = true;
366 physicsNode->m_backendObject = nullptr;
367 }
368 world->m_removedPhysicsNodes.insert(physicsNode);
369 }
370 worldManager.orphanNodes.removeAll(physicsNode);
371}
372
373void QPhysicsWorld::registerContact(QAbstractPhysicsNode *sender, QAbstractPhysicsNode *receiver,
374 const QVector<QVector3D> &positions,
375 const QVector<QVector3D> &impulses,
376 const QVector<QVector3D> &normals)
377{
378 // Since collision callbacks happen in the physx simulation thread we need
379 // to store these callbacks. Otherwise, if an object is deleted in the same
380 // frame a 'onBodyContact' signal is enqueued and a crash will happen.
381 // Therefore we save these contact callbacks and run them at the end of the
382 // physics frame when we know if the objects are deleted or not.
383
384 BodyContact contact;
385 contact.sender = sender;
386 contact.receiver = receiver;
387 contact.positions = positions;
388 contact.impulses = impulses;
389 contact.normals = normals;
390
391 m_registeredContacts.push_back(contact);
392}
393
394QPhysicsWorld::QPhysicsWorld(QObject *parent) : QObject(parent)
395{
396 m_inDesignStudio = !qEnvironmentVariableIsEmpty("QML_PUPPET_MODE");
397 m_physx = new QPhysXWorld;
398 m_physx->createWorld();
399
400 worldManager.worlds.push_back(this);
401 matchOrphanNodes();
402
403 m_frameAnimator = new FrameAnimator;
404 connect(m_frameAnimator, &QQuickFrameAnimation::triggered, this,
405 &QPhysicsWorld::simulateFrame);
406}
407
408QPhysicsWorld::~QPhysicsWorld()
409{
410 if (m_frameAnimator) {
411 m_frameAnimator->stop();
412 delete m_frameAnimator;
413 }
414
415 if (m_physx->scene)
416 m_physx->scene->fetchResults(true);
417
418 for (auto body : m_physXBodies) {
419 body->cleanup(m_physx);
420 delete body;
421 }
422 m_physx->deleteWorld();
423 delete m_physx;
424 worldManager.worlds.removeAll(this);
425
426 if (!qtPhysicsTimingsFile.isEmpty()) {
427 if (m_frameTimings.isEmpty()) {
428 qWarning() << "No frame timings saved.";
429 } else if (auto csvFile = QFile(qtPhysicsTimingsFile); csvFile.open(QIODevice::WriteOnly)) {
430 QTextStream out(&csvFile);
431 for (int i = 1; i < m_frameTimings.size(); i++) {
432 out << i << "," << m_frameTimings[i] << '\n';
433 }
434 csvFile.close();
435 } else {
436 qWarning() << "Could not open timings file " << qtPhysicsTimingsFile;
437 }
438 }
439}
440
441void QPhysicsWorld::classBegin() {}
442
443void QPhysicsWorld::componentComplete()
444{
445 if ((!m_running && !m_inDesignStudio) || m_physicsInitialized)
446 return;
447 initPhysics();
448}
449
450QVector3D QPhysicsWorld::gravity() const
451{
452 return m_gravity;
453}
454
455bool QPhysicsWorld::running() const
456{
457 return m_running;
458}
459
460bool QPhysicsWorld::forceDebugDraw() const
461{
462 return m_forceDebugDraw;
463}
464
465bool QPhysicsWorld::enableCCD() const
466{
467 return m_enableCCD;
468}
469
470float QPhysicsWorld::typicalLength() const
471{
472 return m_typicalLength;
473}
474
475float QPhysicsWorld::typicalSpeed() const
476{
477 return m_typicalSpeed;
478}
479
480bool QPhysicsWorld::isNodeRemoved(QAbstractPhysicsNode *object)
481{
482 return m_removedPhysicsNodes.contains(object);
483}
484
485void QPhysicsWorld::setGravity(QVector3D gravity)
486{
487 if (m_gravity == gravity)
488 return;
489
490 m_gravity = gravity;
491 if (m_physx->scene) {
492 m_physx->scene->setGravity(QPhysicsUtils::toPhysXType(m_gravity));
493 }
494 emit gravityChanged(m_gravity);
495}
496
497void QPhysicsWorld::setRunning(bool running)
498{
499 if (m_running == running)
500 return;
501
502 m_running = running;
503 if (!m_inDesignStudio && m_running && !m_physicsInitialized)
504 initPhysics();
505
506 if (running)
507 m_frameAnimator->start();
508 else
509 m_frameAnimator->stop();
510
511 emit runningChanged(m_running);
512}
513
514void QPhysicsWorld::setForceDebugDraw(bool forceDebugDraw)
515{
516 if (m_forceDebugDraw == forceDebugDraw)
517 return;
518
519 m_forceDebugDraw = forceDebugDraw;
520 if (!m_forceDebugDraw)
521 disableDebugDraw();
522 else
523 updateDebugDraw();
524 emit forceDebugDrawChanged(m_forceDebugDraw);
525}
526
527QQuick3DNode *QPhysicsWorld::viewport() const
528{
529 return m_viewport;
530}
531
532void QPhysicsWorld::setHasIndividualDebugDraw()
533{
534 m_hasIndividualDebugDraw = true;
535}
536
537void QPhysicsWorld::setViewport(QQuick3DNode *viewport)
538{
539 if (m_viewport == viewport)
540 return;
541
542 m_viewport = viewport;
543
544 // TODO: test this
545 for (auto material : m_debugMaterials)
546 delete material;
547 m_debugMaterials.clear();
548
549 for (auto &holder : m_collisionShapeDebugModels) {
550 holder.releaseMeshPointer();
551 delete holder.model;
552 }
553 m_collisionShapeDebugModels.clear();
554
555 emit viewportChanged(m_viewport);
556}
557
558void QPhysicsWorld::setMinimumTimestep(float minTimestep)
559{
560 if (qFuzzyCompare(m_minTimestep, minTimestep))
561 return;
562
563 if (minTimestep > m_maxTimestep) {
564 qWarning("Minimum timestep greater than maximum timestep, value clamped");
565 minTimestep = qMin(minTimestep, m_maxTimestep);
566 }
567
568 if (minTimestep < 0.f) {
569 qWarning("Minimum timestep less than zero, value clamped");
570 minTimestep = qMax(minTimestep, 0.f);
571 }
572
573 if (qFuzzyCompare(m_minTimestep, minTimestep))
574 return;
575
576 m_minTimestep = minTimestep;
577 emit minimumTimestepChanged(m_minTimestep);
578}
579
580void QPhysicsWorld::setMaximumTimestep(float maxTimestep)
581{
582 if (qFuzzyCompare(m_maxTimestep, maxTimestep))
583 return;
584
585 if (maxTimestep < 0.f) {
586 qWarning("Maximum timestep less than zero, value clamped");
587 maxTimestep = qMax(maxTimestep, 0.f);
588 }
589
590 if (qFuzzyCompare(m_maxTimestep, maxTimestep))
591 return;
592
593 m_maxTimestep = maxTimestep;
594 emit maximumTimestepChanged(maxTimestep);
595}
596
597void QPhysicsWorld::setupDebugMaterials(QQuick3DNode *sceneNode)
598{
599 if (!m_debugMaterials.isEmpty())
600 return;
601
602 const int lineWidth = m_inDesignStudio ? 1 : 3;
603
604 // These colors match the indices of DebugDrawBodyType enum
605 for (auto color : { QColorConstants::Svg::chartreuse, QColorConstants::Svg::cyan,
606 QColorConstants::Svg::lightsalmon, QColorConstants::Svg::red,
607 QColorConstants::Svg::blueviolet, QColorConstants::Svg::black }) {
608 auto debugMaterial = new QQuick3DPrincipledMaterial();
609 debugMaterial->setLineWidth(lineWidth);
610 debugMaterial->setParentItem(sceneNode);
611 debugMaterial->setParent(sceneNode);
612 debugMaterial->setBaseColor(color);
613 debugMaterial->setLighting(QQuick3DPrincipledMaterial::NoLighting);
614 debugMaterial->setCullMode(QQuick3DMaterial::NoCulling);
615 m_debugMaterials.push_back(debugMaterial);
616 }
617}
618
619void QPhysicsWorld::updateDebugDraw()
620{
621 if (!(m_forceDebugDraw || m_hasIndividualDebugDraw)) {
622 // Nothing to draw, trash all previous models (if any) and return
623 for (auto &holder : m_collisionShapeDebugModels) {
624 holder.releaseMeshPointer();
625 delete holder.model;
626 }
627 m_collisionShapeDebugModels.clear();
628 return;
629 }
630
631 // Use scene node if no viewport has been specified
632 auto sceneNode = m_viewport ? m_viewport : m_scene;
633
634 if (sceneNode == nullptr)
635 return;
636
637 setupDebugMaterials(sceneNode);
638 m_hasIndividualDebugDraw = false;
639
640 // Store the collision shapes we have now so we can clear out the removed ones
641 QSet<QPair<QAbstractCollisionShape *, QAbstractPhysXNode *>> currentCollisionShapes;
642 currentCollisionShapes.reserve(m_collisionShapeDebugModels.size());
643
644 for (QAbstractPhysXNode *node : m_physXBodies) {
645 if (!node->debugGeometryCapability())
646 continue;
647
648 const auto &collisionShapes = node->frontendNode->getCollisionShapesList();
649 const int materialIdx = static_cast<int>(node->getDebugDrawBodyType());
650 const int length = collisionShapes.length();
651 for (int idx = 0; idx < length; idx++) {
652 const auto collisionShape = collisionShapes[idx];
653
654 if (!m_forceDebugDraw && !collisionShape->enableDebugDraw())
655 continue;
656
657 DebugModelHolder &holder =
658 m_collisionShapeDebugModels[std::make_pair(collisionShape, node)];
659 auto &model = holder.model;
660
661 currentCollisionShapes.insert(std::make_pair(collisionShape, node));
662
663 m_hasIndividualDebugDraw =
664 m_hasIndividualDebugDraw || collisionShape->enableDebugDraw();
665
666 // Create/Update debug view infrastructure
667 if (!model) {
668 model = new QQuick3DModel();
669 model->setParentItem(sceneNode);
670 model->setParent(sceneNode);
671 model->setCastsShadows(false);
672 model->setReceivesShadows(false);
673 model->setCastsReflections(false);
674 }
675
676 model->setVisible(true);
677
678 { // update or set material
679 auto material = m_debugMaterials[materialIdx];
680 QQmlListReference materialsRef(model, "materials");
681 if (materialsRef.count() == 0 || materialsRef.at(0) != material) {
682 materialsRef.clear();
683 materialsRef.append(material);
684 }
685 }
686
687 // Special handling of CharacterController since it has collision shapes,
688 // but not PhysX shapes
689 if (qobject_cast<QCharacterController *>(node->frontendNode)) {
690 QCapsuleShape *capsuleShape = qobject_cast<QCapsuleShape *>(collisionShape);
691 if (!capsuleShape)
692 continue;
693
694 const float radius = capsuleShape->diameter() * 0.5;
695 const float halfHeight = capsuleShape->height() * 0.5;
696
697 if (!qFuzzyCompare(radius, holder.radius())
698 || !qFuzzyCompare(halfHeight, holder.halfHeight())) {
699 auto geom = QDebugDrawHelper::generateCapsuleGeometry(radius, halfHeight);
700 geom->setParent(model);
701 model->setGeometry(geom);
702 holder.setRadius(radius);
703 holder.setHalfHeight(halfHeight);
704 }
705
706 model->setPosition(node->frontendNode->scenePosition());
707 model->setRotation(node->frontendNode->sceneRotation()
708 * QQuaternion::fromEulerAngles(0, 0, 90));
709 continue;
710 }
711
712 if (node->shapes.length() < length)
713 continue;
714
715 const auto physXShape = node->shapes[idx];
716 auto localPose = physXShape->getLocalPose();
717
718 switch (physXShape->getGeometryType()) {
719 case physx::PxGeometryType::eBOX: {
720 physx::PxBoxGeometry boxGeometry;
721 physXShape->getBoxGeometry(boxGeometry);
722 const auto &halfExtentsOld = holder.halfExtents();
723 const auto halfExtents = QPhysicsUtils::toQtType(boxGeometry.halfExtents);
724 if (!qFuzzyCompare(halfExtentsOld, halfExtents)) {
725 auto geom = QDebugDrawHelper::generateBoxGeometry(halfExtents);
726 geom->setParent(model);
727 model->setGeometry(geom);
728 holder.setHalfExtents(halfExtents);
729 }
730
731 }
732 break;
733
734 case physx::PxGeometryType::eSPHERE: {
735 physx::PxSphereGeometry sphereGeometry;
736 physXShape->getSphereGeometry(sphereGeometry);
737 const float radius = holder.radius();
738 if (!qFuzzyCompare(sphereGeometry.radius, radius)) {
739 auto geom = QDebugDrawHelper::generateSphereGeometry(sphereGeometry.radius);
740 geom->setParent(model);
741 model->setGeometry(geom);
742 holder.setRadius(sphereGeometry.radius);
743 }
744 }
745 break;
746
747 case physx::PxGeometryType::eCAPSULE: {
748 physx::PxCapsuleGeometry capsuleGeometry;
749 physXShape->getCapsuleGeometry(capsuleGeometry);
750 const float radius = holder.radius();
751 const float halfHeight = holder.halfHeight();
752
753 if (!qFuzzyCompare(capsuleGeometry.radius, radius)
754 || !qFuzzyCompare(capsuleGeometry.halfHeight, halfHeight)) {
755 auto geom = QDebugDrawHelper::generateCapsuleGeometry(
756 capsuleGeometry.radius, capsuleGeometry.halfHeight);
757 geom->setParent(model);
758 model->setGeometry(geom);
759 holder.setRadius(capsuleGeometry.radius);
760 holder.setHalfHeight(capsuleGeometry.halfHeight);
761 }
762 }
763 break;
764
765 case physx::PxGeometryType::ePLANE:{
766 physx::PxPlaneGeometry planeGeometry;
767 physXShape->getPlaneGeometry(planeGeometry);
768 // Special rotation
769 const QQuaternion rotation =
770 QPhysicsUtils::kMinus90YawRotation * QPhysicsUtils::toQtType(localPose.q);
771 localPose = physx::PxTransform(localPose.p, QPhysicsUtils::toPhysXType(rotation));
772
773 if (model->geometry() == nullptr) {
774 auto geom = QDebugDrawHelper::generatePlaneGeometry();
775 geom->setParent(model);
776 model->setGeometry(geom);
777 }
778 }
779 break;
780
781 // For heightfield, convex mesh and triangle mesh we increase its reference count
782 // to make sure it does not get dereferenced and deleted so that the new mesh will
783 // have another memory address so we know when it has changed.
784 case physx::PxGeometryType::eHEIGHTFIELD: {
785 physx::PxHeightFieldGeometry heightFieldGeometry;
786 bool success = physXShape->getHeightFieldGeometry(heightFieldGeometry);
787 Q_ASSERT(success);
788 const float heightScale = holder.heightScale();
789 const float rowScale = holder.rowScale();
790 const float columnScale = holder.columnScale();
791
792 if (auto heightField = holder.getHeightField();
793 heightField && heightField != heightFieldGeometry.heightField) {
794 heightField->release();
795 holder.setHeightField(nullptr);
796 }
797
798 if (!qFuzzyCompare(heightFieldGeometry.heightScale, heightScale)
799 || !qFuzzyCompare(heightFieldGeometry.rowScale, rowScale)
800 || !qFuzzyCompare(heightFieldGeometry.columnScale, columnScale)
801 || !holder.getHeightField()) {
802 if (!holder.getHeightField()) {
803 heightFieldGeometry.heightField->acquireReference();
804 holder.setHeightField(heightFieldGeometry.heightField);
805 }
806 auto geom = QDebugDrawHelper::generateHeightFieldGeometry(
807 heightFieldGeometry.heightField, heightFieldGeometry.heightScale,
808 heightFieldGeometry.rowScale, heightFieldGeometry.columnScale);
809 geom->setParent(model);
810 model->setGeometry(geom);
811 holder.setHeightScale(heightFieldGeometry.heightScale);
812 holder.setRowScale(heightFieldGeometry.rowScale);
813 holder.setColumnScale(heightFieldGeometry.columnScale);
814 }
815 }
816 break;
817
818 case physx::PxGeometryType::eCONVEXMESH: {
819 physx::PxConvexMeshGeometry convexMeshGeometry;
820 const bool success = physXShape->getConvexMeshGeometry(convexMeshGeometry);
821 Q_ASSERT(success);
822 const auto rotation = convexMeshGeometry.scale.rotation * localPose.q;
823 localPose = physx::PxTransform(localPose.p, rotation);
824 model->setScale(QPhysicsUtils::toQtType(convexMeshGeometry.scale.scale));
825
826 if (auto convexMesh = holder.getConvexMesh();
827 convexMesh && convexMesh != convexMeshGeometry.convexMesh) {
828 convexMesh->release();
829 holder.setConvexMesh(nullptr);
830 }
831
832 if (!model->geometry() || !holder.getConvexMesh()) {
833 if (!holder.getConvexMesh()) {
834 convexMeshGeometry.convexMesh->acquireReference();
835 holder.setConvexMesh(convexMeshGeometry.convexMesh);
836 }
837 auto geom = QDebugDrawHelper::generateConvexMeshGeometry(
838 convexMeshGeometry.convexMesh);
839 geom->setParent(model);
840 model->setGeometry(geom);
841 }
842 }
843 break;
844
845 case physx::PxGeometryType::eTRIANGLEMESH: {
846 physx::PxTriangleMeshGeometry triangleMeshGeometry;
847 const bool success = physXShape->getTriangleMeshGeometry(triangleMeshGeometry);
848 Q_ASSERT(success);
849 const auto rotation = triangleMeshGeometry.scale.rotation * localPose.q;
850 localPose = physx::PxTransform(localPose.p, rotation);
851 model->setScale(QPhysicsUtils::toQtType(triangleMeshGeometry.scale.scale));
852
853 if (auto triangleMesh = holder.getTriangleMesh();
854 triangleMesh && triangleMesh != triangleMeshGeometry.triangleMesh) {
855 triangleMesh->release();
856 holder.setTriangleMesh(nullptr);
857 }
858
859 if (!model->geometry() || !holder.getTriangleMesh()) {
860 if (!holder.getTriangleMesh()) {
861 triangleMeshGeometry.triangleMesh->acquireReference();
862 holder.setTriangleMesh(triangleMeshGeometry.triangleMesh);
863 }
864 auto geom = QDebugDrawHelper::generateTriangleMeshGeometry(
865 triangleMeshGeometry.triangleMesh);
866 geom->setParent(model);
867 model->setGeometry(geom);
868 }
869 }
870 break;
871
872 case physx::PxGeometryType::eINVALID:
873 case physx::PxGeometryType::eGEOMETRY_COUNT:
874 // should not happen
875 Q_UNREACHABLE();
876 }
877
878 auto globalPose = node->getGlobalPose();
879 auto finalPose = globalPose.transform(localPose);
880
881 model->setRotation(QPhysicsUtils::toQtType(finalPose.q));
882 model->setPosition(QPhysicsUtils::toQtType(finalPose.p));
883 }
884 }
885
886 // Remove old collision shapes
887 m_collisionShapeDebugModels.removeIf(
888 [&](QHash<QPair<QAbstractCollisionShape *, QAbstractPhysXNode *>,
889 DebugModelHolder>::iterator it) {
890 if (!currentCollisionShapes.contains(it.key())) {
891 auto holder = it.value();
892 holder.releaseMeshPointer();
893 if (holder.model)
894 delete holder.model;
895 return true;
896 }
897 return false;
898 });
899}
900
901static void collectPhysicsNodes(QQuick3DObject *node, QList<QAbstractPhysicsNode *> &nodes)
902{
903 if (auto shape = qobject_cast<QAbstractPhysicsNode *>(node)) {
904 nodes.push_back(shape);
905 return;
906 }
907
908 for (QQuick3DObject *child : node->childItems())
909 collectPhysicsNodes(child, nodes);
910}
911
912void QPhysicsWorld::updateDebugDrawDesignStudio()
913{
914 // Use scene node if no viewport has been specified
915 auto sceneNode = m_viewport ? m_viewport : m_scene;
916
917 if (sceneNode == nullptr)
918 return;
919
920 setupDebugMaterials(sceneNode);
921
922 // Store the collision shapes we have now so we can clear out the removed ones
923 QSet<QPair<QAbstractCollisionShape *, QAbstractPhysicsNode *>> currentCollisionShapes;
924 currentCollisionShapes.reserve(m_collisionShapeDebugModels.size());
925
926 QList<QAbstractPhysicsNode *> activePhysicsNodes;
927 activePhysicsNodes.reserve(m_collisionShapeDebugModels.size());
928 collectPhysicsNodes(m_scene, activePhysicsNodes);
929
930 for (QAbstractPhysicsNode *node : activePhysicsNodes) {
931
932 const auto &collisionShapes = node->getCollisionShapesList();
933 const int materialIdx = 0; // Just take first material
934 const int length = collisionShapes.length();
935
936 const bool isCharacterController = qobject_cast<QCharacterController *>(node) != nullptr;
937
938 for (int idx = 0; idx < length; idx++) {
939 QAbstractCollisionShape *collisionShape = collisionShapes[idx];
940 DebugModelHolder &holder =
941 m_DesignStudioDebugModels[std::make_pair(collisionShape, node)];
942 auto &model = holder.model;
943
944 currentCollisionShapes.insert(std::make_pair(collisionShape, node));
945
946 m_hasIndividualDebugDraw =
947 m_hasIndividualDebugDraw || collisionShape->enableDebugDraw();
948
949 // Create/Update debug view infrastructure
950 {
951 // Hack: we have to delete the model every frame so it shows up in QDS
952 // whenever the code is updated, not sure why ¯\_(?)_/¯
953 delete model;
954 model = new QQuick3DModel();
955 model->setParentItem(sceneNode);
956 model->setParent(sceneNode);
957 model->setCastsShadows(false);
958 model->setReceivesShadows(false);
959 model->setCastsReflections(false);
960 }
961
962 const bool hasGeometry = holder.geometry != nullptr;
963 QVector3D scenePosition = collisionShape->scenePosition();
964 QQuaternion sceneRotation = collisionShape->sceneRotation();
965 QQuick3DGeometry *newGeometry = nullptr;
966
967 if (isCharacterController)
968 sceneRotation = sceneRotation * QQuaternion::fromEulerAngles(QVector3D(0, 0, 90));
969
970 { // update or set material
971 auto material = m_debugMaterials[materialIdx];
972 QQmlListReference materialsRef(model, "materials");
973 if (materialsRef.count() == 0 || materialsRef.at(0) != material) {
974 materialsRef.clear();
975 materialsRef.append(material);
976 }
977 }
978
979 if (auto shape = qobject_cast<QBoxShape *>(collisionShape)) {
980 const auto &halfExtentsOld = holder.halfExtents();
981 const auto halfExtents = shape->sceneScale() * shape->extents() * 0.5f;
982 if (!qFuzzyCompare(halfExtentsOld, halfExtents) || !hasGeometry) {
983 newGeometry = QDebugDrawHelper::generateBoxGeometry(halfExtents);
984 holder.setHalfExtents(halfExtents);
985 }
986 } else if (auto shape = qobject_cast<QSphereShape *>(collisionShape)) {
987 const float radiusOld = holder.radius();
988 const float radius = shape->sceneScale().x() * shape->diameter() * 0.5f;
989 if (!qFuzzyCompare(radiusOld, radius) || !hasGeometry) {
990 newGeometry = QDebugDrawHelper::generateSphereGeometry(radius);
991 holder.setRadius(radius);
992 }
993 } else if (auto shape = qobject_cast<QCapsuleShape *>(collisionShape)) {
994 const float radiusOld = holder.radius();
995 const float halfHeightOld = holder.halfHeight();
996 const float radius = shape->sceneScale().y() * shape->diameter() * 0.5f;
997 const float halfHeight = shape->sceneScale().x() * shape->height() * 0.5f;
998
999 if ((!qFuzzyCompare(radiusOld, radius) || !qFuzzyCompare(halfHeightOld, halfHeight))
1000 || !hasGeometry) {
1001 newGeometry = QDebugDrawHelper::generateCapsuleGeometry(radius, halfHeight);
1002 holder.setRadius(radius);
1003 holder.setHalfHeight(halfHeight);
1004 }
1005 } else if (qobject_cast<QPlaneShape *>(collisionShape)) {
1006 if (!hasGeometry)
1007 newGeometry = QDebugDrawHelper::generatePlaneGeometry();
1008 } else if (auto shape = qobject_cast<QHeightFieldShape *>(collisionShape)) {
1009 physx::PxHeightFieldGeometry *heightFieldGeometry =
1010 static_cast<physx::PxHeightFieldGeometry *>(shape->getPhysXGeometry());
1011 const float heightScale = holder.heightScale();
1012 const float rowScale = holder.rowScale();
1013 const float columnScale = holder.columnScale();
1014 scenePosition += shape->hfOffset();
1015 if (!heightFieldGeometry) {
1016 qWarning() << "Could not get height field";
1017 } else if (!qFuzzyCompare(heightFieldGeometry->heightScale, heightScale)
1018 || !qFuzzyCompare(heightFieldGeometry->rowScale, rowScale)
1019 || !qFuzzyCompare(heightFieldGeometry->columnScale, columnScale)
1020 || !hasGeometry) {
1021 newGeometry = QDebugDrawHelper::generateHeightFieldGeometry(
1022 heightFieldGeometry->heightField, heightFieldGeometry->heightScale,
1023 heightFieldGeometry->rowScale, heightFieldGeometry->columnScale);
1024 holder.setHeightScale(heightFieldGeometry->heightScale);
1025 holder.setRowScale(heightFieldGeometry->rowScale);
1026 holder.setColumnScale(heightFieldGeometry->columnScale);
1027 }
1028 } else if (auto shape = qobject_cast<QConvexMeshShape *>(collisionShape)) {
1029 auto convexMeshGeometry =
1030 static_cast<physx::PxConvexMeshGeometry *>(shape->getPhysXGeometry());
1031 if (!convexMeshGeometry) {
1032 qWarning() << "Could not get convex mesh";
1033 } else {
1034 model->setScale(QPhysicsUtils::toQtType(convexMeshGeometry->scale.scale));
1035
1036 if (!hasGeometry) {
1037 newGeometry = QDebugDrawHelper::generateConvexMeshGeometry(
1038 convexMeshGeometry->convexMesh);
1039 }
1040 }
1041 } else if (auto shape = qobject_cast<QTriangleMeshShape *>(collisionShape)) {
1042 physx::PxTriangleMeshGeometry *triangleMeshGeometry =
1043 static_cast<physx::PxTriangleMeshGeometry *>(shape->getPhysXGeometry());
1044 if (!triangleMeshGeometry) {
1045 qWarning() << "Could not get triangle mesh";
1046 } else {
1047 model->setScale(QPhysicsUtils::toQtType(triangleMeshGeometry->scale.scale));
1048
1049 if (!hasGeometry) {
1050 newGeometry = QDebugDrawHelper::generateTriangleMeshGeometry(
1051 triangleMeshGeometry->triangleMesh);
1052 }
1053 }
1054 }
1055
1056 if (newGeometry) {
1057 delete holder.geometry;
1058 holder.geometry = newGeometry;
1059 }
1060
1061 model->setGeometry(holder.geometry);
1062 model->setVisible(true);
1063
1064 model->setRotation(sceneRotation);
1065 model->setPosition(scenePosition);
1066 }
1067 }
1068
1069 // Remove old debug models
1070 m_DesignStudioDebugModels.removeIf(
1071 [&](QHash<QPair<QAbstractCollisionShape *, QAbstractPhysicsNode *>,
1072 DebugModelHolder>::iterator it) {
1073 if (!currentCollisionShapes.contains(it.key())) {
1074 auto holder = it.value();
1075 holder.releaseMeshPointer();
1076 if (holder.model) {
1077 delete holder.geometry;
1078 delete holder.model;
1079 }
1080 return true;
1081 }
1082 return false;
1083 });
1084}
1085
1086void QPhysicsWorld::disableDebugDraw()
1087{
1088 m_hasIndividualDebugDraw = false;
1089
1090 for (QAbstractPhysXNode *body : m_physXBodies) {
1091 const auto &collisionShapes = body->frontendNode->getCollisionShapesList();
1092 const int length = collisionShapes.length();
1093 for (int idx = 0; idx < length; idx++) {
1094 const auto collisionShape = collisionShapes[idx];
1095 if (collisionShape->enableDebugDraw()) {
1096 m_hasIndividualDebugDraw = true;
1097 return;
1098 }
1099 }
1100 }
1101}
1102
1103void QPhysicsWorld::setEnableCCD(bool enableCCD)
1104{
1105 if (m_enableCCD == enableCCD)
1106 return;
1107
1108 if (m_physicsInitialized) {
1109 qWarning()
1110 << "Warning: Changing 'enableCCD' after physics is initialized will have no effect";
1111 return;
1112 }
1113
1114 m_enableCCD = enableCCD;
1115 emit enableCCDChanged(m_enableCCD);
1116}
1117
1118void QPhysicsWorld::setTypicalLength(float typicalLength)
1119{
1120 if (qFuzzyCompare(typicalLength, m_typicalLength))
1121 return;
1122
1123 if (typicalLength <= 0.f) {
1124 qWarning() << "Warning: 'typicalLength' value less than zero, ignored";
1125 return;
1126 }
1127
1128 if (m_physicsInitialized) {
1129 qWarning() << "Warning: Changing 'typicalLength' after physics is initialized will have "
1130 "no effect";
1131 return;
1132 }
1133
1134 m_typicalLength = typicalLength;
1135
1136 emit typicalLengthChanged(typicalLength);
1137}
1138
1139void QPhysicsWorld::setTypicalSpeed(float typicalSpeed)
1140{
1141 if (qFuzzyCompare(typicalSpeed, m_typicalSpeed))
1142 return;
1143
1144 if (m_physicsInitialized) {
1145 qWarning() << "Warning: Changing 'typicalSpeed' after physics is initialized will have "
1146 "no effect";
1147 return;
1148 }
1149
1150 m_typicalSpeed = typicalSpeed;
1151
1152 emit typicalSpeedChanged(typicalSpeed);
1153}
1154
1155float QPhysicsWorld::defaultDensity() const
1156{
1157 return m_defaultDensity;
1158}
1159
1160float QPhysicsWorld::minimumTimestep() const
1161{
1162 return m_minTimestep;
1163}
1164
1165float QPhysicsWorld::maximumTimestep() const
1166{
1167 return m_maxTimestep;
1168}
1169
1170void QPhysicsWorld::setDefaultDensity(float defaultDensity)
1171{
1172 if (qFuzzyCompare(m_defaultDensity, defaultDensity))
1173 return;
1174 m_defaultDensity = defaultDensity;
1175
1176 // Go through all dynamic rigid bodies and update the default density
1177 for (QAbstractPhysXNode *body : m_physXBodies)
1178 body->updateDefaultDensity(m_defaultDensity);
1179
1180 emit defaultDensityChanged(defaultDensity);
1181}
1182
1183// Remove physics world items that no longer exist
1184
1185void QPhysicsWorld::cleanupRemovedNodes()
1186{
1187 m_physXBodies.removeIf([this](QAbstractPhysXNode *body) {
1188 return body->cleanupIfRemoved(m_physx);
1189 });
1190 m_removedPhysicsNodes.clear();
1191}
1192
1193void QPhysicsWorld::initPhysics()
1194{
1195 Q_ASSERT(!m_physicsInitialized);
1196
1197 const unsigned int numThreads = m_numThreads >= 0 ? m_numThreads : qMax(0, QThread::idealThreadCount());
1198 m_physx->createScene(m_typicalLength, m_typicalSpeed, m_gravity, m_enableCCD, this, numThreads);
1199 m_frameAnimator->start();
1200 m_physicsInitialized = true;
1201}
1202
1203void QPhysicsWorld::simulateFrame()
1204{
1205 constexpr double MILLIONTH = 0.000001;
1206 constexpr double THOUSANDTH = 0.001;
1207
1208 if (m_inDesignStudio) {
1209 frameFinishedDesignStudio();
1210 return;
1211 }
1212
1213 if (!m_physx->isRunning) {
1214 m_timer.start();
1215 m_physx->isRunning = true;
1216 const double minTimestepSecs = m_minTimestep * 0.001;
1217 m_physx->scene->simulate(minTimestepSecs);
1218 m_currTimeStep = minTimestepSecs;
1219 return;
1220 }
1221
1222 // Frame not ready yet
1223 if (!m_frameFetched && !m_physx->scene->checkResults()) {
1224 return;
1225 }
1226
1227 // Frame ready, fetch and finish it
1228 if (!m_frameFetched) {
1229 m_physx->scene->fetchResults(true);
1230 frameFinished(m_currTimeStep);
1231 m_frameFetched = true;
1232 if (Q_UNLIKELY(!qtPhysicsTimingsFile.isEmpty())) {
1233 const double deltaMS = m_timer.nsecsElapsed() * MILLIONTH;
1234 m_frameTimings.append(deltaMS);
1235 }
1236 }
1237
1238 // Assuming: 0 <= minTimestep <= maxTimestep
1239 const double deltaMS = m_timer.nsecsElapsed() * MILLIONTH;
1240 if (deltaMS < m_minTimestep)
1241 return;
1242 const double deltaSecs = qMin<double>(deltaMS, m_maxTimestep) * THOUSANDTH;
1243 m_timer.restart();
1244 m_physx->scene->simulate(deltaSecs);
1245 m_frameFetched = false;
1246 m_currTimeStep = deltaSecs;
1247}
1248
1249void QPhysicsWorld::frameFinished(float deltaTime)
1250{
1251 matchOrphanNodes();
1252 emitContactCallbacks();
1253 cleanupRemovedNodes();
1254 for (auto *node : std::as_const(m_newPhysicsNodes)) {
1255 auto *body = node->createPhysXBackend();
1256 body->init(this, m_physx);
1257 m_physXBodies.push_back(body);
1258 }
1259 m_newPhysicsNodes.clear();
1260
1261 QHash<QQuick3DNode *, QMatrix4x4> transformCache;
1262
1263 // TODO: Use dirty flag/dirty list to avoid redoing things that didn't change
1264 for (auto *physXBody : std::as_const(m_physXBodies)) {
1265 physXBody->markDirtyShapes();
1266 physXBody->rebuildDirtyShapes(this, m_physx);
1267 physXBody->updateFilters();
1268
1269 // Sync the physics world and the scene
1270 physXBody->sync(deltaTime, transformCache);
1271 }
1272
1273 updateDebugDraw();
1274 emit frameDone(deltaTime * 1000);
1275}
1276
1277void QPhysicsWorld::frameFinishedDesignStudio()
1278{
1279 // Note sure if this is needed but do it anyway
1280 matchOrphanNodes();
1281 emitContactCallbacks();
1282 cleanupRemovedNodes();
1283 // Ignore new physics nodes, we find them from the scene node anyway
1284 m_newPhysicsNodes.clear();
1285
1286 updateDebugDrawDesignStudio();
1287}
1288
1289QPhysicsWorld *QPhysicsWorld::getWorld(QQuick3DNode *node)
1290{
1291 for (QPhysicsWorld *world : worldManager.worlds) {
1292 if (!world->m_scene) {
1293 continue;
1294 }
1295
1296 QQuick3DNode *nodeCurr = node;
1297
1298 // Maybe pointless but check starting node
1299 if (nodeCurr == world->m_scene)
1300 return world;
1301
1302 while (nodeCurr->parentNode()) {
1303 nodeCurr = nodeCurr->parentNode();
1304 if (nodeCurr == world->m_scene)
1305 return world;
1306 }
1307 }
1308
1309 return nullptr;
1310}
1311
1312void QPhysicsWorld::matchOrphanNodes()
1313{
1314 // FIXME: does this need thread safety?
1315 if (worldManager.orphanNodes.isEmpty())
1316 return;
1317
1318 qsizetype numNodes = worldManager.orphanNodes.length();
1319 qsizetype idx = 0;
1320
1321 while (idx < numNodes) {
1322 auto node = worldManager.orphanNodes[idx];
1323 auto world = getWorld(node);
1324 if (world == this) {
1325 world->m_newPhysicsNodes.push_back(node);
1326 // swap-erase
1327 worldManager.orphanNodes.swapItemsAt(idx, numNodes - 1);
1328 worldManager.orphanNodes.pop_back();
1329 numNodes--;
1330 } else {
1331 idx++;
1332 }
1333 }
1334}
1335
1336void QPhysicsWorld::findPhysicsNodes()
1337{
1338 // This method finds the physics nodes inside the scene pointed to by the
1339 // scene property. This method is necessary to run whenever the scene
1340 // property is changed.
1341 if (m_scene == nullptr)
1342 return;
1343
1344 // Recursively go through all children and add all QAbstractPhysicsNode's
1345 QList<QQuick3DObject *> children = m_scene->childItems();
1346 while (!children.empty()) {
1347 auto child = children.takeFirst();
1348 if (auto converted = qobject_cast<QAbstractPhysicsNode *>(child); converted != nullptr) {
1349 // This should never happen but check anyway.
1350 if (converted->m_backendObject != nullptr) {
1351 qWarning() << "Warning: physics node already associated with a backend node.";
1352 continue;
1353 }
1354
1355 m_newPhysicsNodes.push_back(converted);
1356 worldManager.orphanNodes.removeAll(converted); // No longer orphan
1357 }
1358 children.append(child->childItems());
1359 }
1360}
1361
1362void QPhysicsWorld::emitContactCallbacks()
1363{
1364 for (const QPhysicsWorld::BodyContact &contact : m_registeredContacts) {
1365 if (m_removedPhysicsNodes.contains(contact.sender)
1366 || m_removedPhysicsNodes.contains(contact.receiver))
1367 continue;
1368 contact.receiver->registerContact(contact.sender, contact.positions, contact.impulses,
1369 contact.normals);
1370 }
1371
1372 m_registeredContacts.clear();
1373}
1374
1375physx::PxPhysics *QPhysicsWorld::getPhysics()
1376{
1377 return StaticPhysXObjects::getReference().physics;
1378}
1379
1380physx::PxCooking *QPhysicsWorld::getCooking()
1381{
1382 return StaticPhysXObjects::getReference().cooking;
1383}
1384
1385physx::PxControllerManager *QPhysicsWorld::controllerManager()
1386{
1387 if (m_physx->scene && !m_physx->controllerManager) {
1388 m_physx->controllerManager = PxCreateControllerManager(*m_physx->scene);
1389 qCDebug(lcQuick3dPhysics) << "Created controller manager" << m_physx->controllerManager;
1390 }
1391 return m_physx->controllerManager;
1392}
1393
1394QQuick3DNode *QPhysicsWorld::scene() const
1395{
1396 return m_scene;
1397}
1398
1399void QPhysicsWorld::setScene(QQuick3DNode *newScene)
1400{
1401 if (m_scene == newScene)
1402 return;
1403
1404 m_scene = newScene;
1405
1406 // Delete all nodes since they are associated with the previous scene
1407 for (auto body : m_physXBodies) {
1408 deregisterNode(body->frontendNode);
1409 }
1410
1411 // Check if scene is already used by another world
1412 bool sceneOK = true;
1413 for (QPhysicsWorld *world : worldManager.worlds) {
1414 if (world != this && world->scene() == newScene) {
1415 sceneOK = false;
1416 qWarning() << "Warning: scene already associated with physics world";
1417 }
1418 }
1419
1420 if (sceneOK)
1421 findPhysicsNodes();
1422 emit sceneChanged();
1423}
1424
1425int QPhysicsWorld::numThreads() const
1426{
1427 return m_numThreads;
1428}
1429
1430void QPhysicsWorld::setNumThreads(int newNumThreads)
1431{
1432 if (m_numThreads == newNumThreads)
1433 return;
1434 m_numThreads = newNumThreads;
1435 emit numThreadsChanged();
1436}
1437
1438bool QPhysicsWorld::reportKinematicKinematicCollisions() const
1439{
1440 return m_reportKinematicKinematicCollisions;
1441}
1442
1443void QPhysicsWorld::setReportKinematicKinematicCollisions(
1444 bool newReportKinematicKinematicCollisions)
1445{
1446 if (m_reportKinematicKinematicCollisions == newReportKinematicKinematicCollisions)
1447 return;
1448 m_reportKinematicKinematicCollisions = newReportKinematicKinematicCollisions;
1449 emit reportKinematicKinematicCollisionsChanged();
1450}
1451
1452bool QPhysicsWorld::reportStaticKinematicCollisions() const
1453{
1454 return m_reportStaticKinematicCollisions;
1455}
1456
1457void QPhysicsWorld::setReportStaticKinematicCollisions(bool newReportStaticKinematicCollisions)
1458{
1459 if (m_reportStaticKinematicCollisions == newReportStaticKinematicCollisions)
1460 return;
1461 m_reportStaticKinematicCollisions = newReportStaticKinematicCollisions;
1462 emit reportStaticKinematicCollisionsChanged();
1463}
1464
1466
1467#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
#define QT_END_NAMESPACE
QVector< QAbstractPhysicsNode * > orphanNodes
QVector< QPhysicsWorld * > worlds