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"
12#include "joints/qjoint_p.h"
14#include "qboxshape_p.h"
15#include "qsphereshape_p.h"
20#include "qplaneshape_p.h"
22
23#include "PxPhysicsAPI.h"
24#include "cooking/PxCooking.h"
25
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>
32
33#include <QtGui/qquaternion.h>
34
35#include <QtEnvironmentVariables>
36
37#define PHYSX_ENABLE_PVD 0
38
40
41/*!
42 \qmltype PhysicsWorld
43 \inqmlmodule QtQuick3D.Physics
44 \since 6.4
45 \brief Controls the physics simulation.
46
47 The PhysicsWorld type controls the physics simulation. This node is used to create an instance of the physics world as well
48 as define its properties. There can only be one physics world. All collision nodes in the qml
49 will get added automatically to the physics world.
50*/
51
52/*!
53 \qmlproperty vector3d PhysicsWorld::gravity
54 This property defines the gravity vector of the physics world.
55 The default value is \c (0, -981, 0). Set the value to \c{Qt.vector3d(0, -9.81, 0)} if your
56 unit of measurement is meters and you are simulating Earth gravity.
57*/
58
59/*!
60 \qmlproperty bool PhysicsWorld::running
61 This property starts or stops the physical simulation. The default value is \c true.
62*/
63
64/*!
65 \qmlproperty bool PhysicsWorld::forceDebugDraw
66 This property enables debug drawing of all active shapes in the physics world. The default value
67 is \c false.
68*/
69
70/*!
71 \qmlproperty bool PhysicsWorld::enableCCD
72 \deprecated [6.13] Use DynamicRigidBody::ccd instead.
73
74 This property enables continuous collision detection globally for every dynamic body
75 in the scene that does not explicitly set its own \l DynamicRigidBody::ccd mode.
76 It reduces the risk of fast-moving bodies passing through geometry at high velocities
77 (also known as tunnelling).
78
79 For non-kinematic dynamic bodies, enabling this property uses sweep-based CCD.
80 For kinematic bodies, it automatically uses speculative CCD, as sweep-based CCD
81 is not supported for them.
82
83 Continuous collision detection can also be configured per body via
84 \l DynamicRigidBody::ccd, which avoids paying its performance cost for
85 objects that do not need it.
86
87 \warning Using trigger bodies with CCD enabled is not supported and can
88 result in missing or false trigger reports.
89
90 \default false
91
92 \sa DynamicRigidBody::ccd
93*/
94
95/*!
96 \qmlproperty real PhysicsWorld::typicalLength
97 This property defines the approximate size of objects in the simulation. This is used to
98 estimate certain length-related tolerances. Objects much smaller or much larger than this
99 size may not behave properly. The default value is \c 100.
100
101 Range: \c{[0, inf]}
102*/
103
104/*!
105 \qmlproperty real PhysicsWorld::typicalSpeed
106 This property defines the typical magnitude of velocities of objects in simulation. This is used
107 to estimate whether a contact should be treated as bouncing or resting based on its impact
108 velocity, and a kinetic energy threshold below which the simulation may put objects to sleep.
109
110 For normal physical environments, a good choice is the approximate speed of an object falling
111 under gravity for one second. The default value is \c 1000.
112
113 Range: \c{[0, inf]}
114*/
115
116/*!
117 \qmlproperty real PhysicsWorld::defaultDensity
118 This property defines the default density of dynamic objects, measured in kilograms per cubic
119 unit. This is equal to the weight of a cube with side \c 1.
120
121 The default value is \c 0.001, corresponding to 1 g/cm³: the density of water. If your unit of
122 measurement is meters, a good value would be \c 1000. Note that only positive values are
123 allowed.
124
125 Range: \c{(0, inf]}
126*/
127
128/*!
129 \qmlproperty Node PhysicsWorld::viewport
130 This property defines the viewport where debug components will be drawn if \l{forceDebugDraw}
131 is enabled. If unset the \l{scene} node will be used.
132
133 \sa forceDebugDraw, scene
134*/
135
136/*!
137 \qmlproperty real PhysicsWorld::minimumTimestep
138 This property defines the minimum simulation timestep in milliseconds. The default value is
139 \c{1}.
140
141 Range: \c{[0, maximumTimestep]}
142
143 \note The simulation timestep works in lockstep with the rendering,
144 meaning a new simulation frame will only be started after a rendered frame
145 has completed. This means that at most one simulation frame will run per
146 rendered frame.
147*/
148
149/*!
150 \qmlproperty real PhysicsWorld::maximumTimestep
151 This property defines the maximum simulation timestep in milliseconds. The default value is
152 \c{33.333}.
153
154 Range: \c{[0, inf]}
155
156 \note The simulation timestep works in lockstep with the rendering,
157 meaning a new simulation frame will only be started after a rendered frame
158 has completed. This means that at most one simulation frame will run per
159 rendered frame.
160*/
161
162/*!
163 \qmlproperty Node PhysicsWorld::scene
164
165 This property defines the top-most Node that contains all the nodes of the physical
166 simulation. All physics objects that are an ancestor of this node will be seen as part of this
167 PhysicsWorld.
168
169 \note Using the same scene node for several PhysicsWorld is unsupported.
170*/
171
172/*!
173 \qmlsignal PhysicsWorld::frameDone(float timestep)
174 \since 6.5
175
176 This signal is emitted when the physical simulation is done simulating a frame. The \a timestep
177 parameter is how long in milliseconds the timestep was in the simulation.
178*/
179
180/*!
181 \qmlproperty int PhysicsWorld::numThreads
182 \since 6.7
183
184 This property defines the number of threads used for the physical simulation. This is how the
185 range of values are interpreted:
186
187 \table
188 \header
189 \li Value
190 \li Range
191 \li Description
192 \row
193 \li Negative
194 \li \c{[-inf, -1]}
195 \li Automatic thread count. The application will try to query the number of threads from the
196 system.
197 \row
198 \li Zero
199 \li \c{{0}}
200 \li No threading, simulation will run sequentially.
201 \row
202 \li Positive
203 \li \c{[1, inf]}
204 \li Specific thread count.
205 \endtable
206
207 The default value is \c{-1}, meaning automatic thread count.
208
209 \note Once the scene has started running it is not possible to change the number of threads.
210*/
211
212/*!
213 \qmlproperty bool PhysicsWorld::reportKinematicKinematicCollisions
214 \since 6.7
215
216 This property controls if collisions between pairs of \e{kinematic} dynamic rigid bodies will
217 trigger a contact report.
218
219 The default value is \c{false}.
220
221 \note Once the scene has started running it is not possible to change this setting.
222 \sa PhysicsWorld::reportStaticKinematicCollisions
223 \sa DynamicRigidBody
224 \sa PhysicsNode::bodyContact
225*/
226
227/*!
228 \qmlproperty bool PhysicsWorld::reportStaticKinematicCollisions
229 \since 6.7
230
231 This property controls if collisions between a static rigid body and a \e{kinematic} dynamic
232 rigid body will trigger a contact report.
233
234 The default value is \c{false}.
235
236 \note Once the scene has started running it is not possible to change this setting.
237 \sa PhysicsWorld::reportKinematicKinematicCollisions
238 \sa StaticRigidBody
239 \sa DynamicRigidBody
240 \sa PhysicsNode::bodyContact
241*/
242
243/*!
244 \qmlproperty QueryStructure PhysicsWorld::staticQueryStructure
245 \since 6.13
246
247 The spatial pruning structure type used to accelerate scene queries
248 (raycasts, sweeps, overlaps - used in CharacterController and a \e{kinematic} dynamic rigid body)
249 against static actors in the physics scene. It has no effect on rigid-body
250 contact/collision detection during simulation.
251
252 The following values are available:
253
254 \value PhysicsWorld.StaticTree
255 Uses a pre-baked static AABB tree. Offers maximum scene query performance with zero per-frame management overhead.
256 Best for fully static, immutable scenes. Inserting or removing objects at runtime causes heavy scene rebuilds and frame spikes.
257 \value PhysicsWorld.DynamicTree
258 Uses a dynamic self-balancing AABB tree. Allows fast, local runtime insertion (logarithmic time) and removal of static objects without freezing the frame.
259 Ideal for seamless open-world streaming where static chunks are loaded dynamically.
260
261 \default PhysicsWorld.DynamicTree
262
263 \note Once the scene has started running it is not possible to change this setting.
264 \note PhysicsWorld.NoStructure is not supported for staticQueryStructure
265
266 \sa PhysicsWorld::dynamicQueryStructure
267
268*/
269
270/*!
271 \qmlproperty QueryStructure PhysicsWorld::dynamicQueryStructure
272 \since 6.13
273
274 The spatial pruning structure type used to accelerate scene queries
275 (raycasts, sweeps, overlaps - used in CharacterController and a \e{kinematic} dynamic rigid body)
276 against dynamic actors in the physics scene. It has no effect on rigid-body
277 contact/collision detection during simulation.
278
279 The following values are available:
280
281 \value PhysicsWorld.NoStructure
282 Disables the scene query acceleration structure for dynamic actors.
283 Eliminates CPU overhead for tree maintenance when objects move, spawn, or are destroyed.
284 Scene queries will fall back to a linear search. Ideal when scene queries are not needed for dynamic objects.
285 \value PhysicsWorld.StaticTree
286 Uses a static AABB tree. Offers faster scene queries for dynamic actors, but updating the tree when objects move or spawn is very expensive.
287 Best when dynamic actors rarely move or spend most of their time sleeping.
288 \value PhysicsWorld.DynamicTree
289 Uses a dynamic self-balancing AABB tree. Allows fast, local runtime insertion (logarithmic time), movement, and removal of dynamic objects without freezing the frame.
290 Ideal for active scenes with frequently moving or spawning dynamic actors.
291
292 \default PhysicsWorld.DynamicTree
293
294 \note Once the scene has started running it is not possible to change this setting.
295 \sa PhysicsWorld::staticQueryStructure
296*/
297
298Q_LOGGING_CATEGORY(lcQuick3dPhysics, "qt.quick3d.physics");
299
300// Setting QT_PHYSICS_TIMINGS_FILE to a filepath will generate a csv file with frame timings.
301// Note that if running several PhysicsWorld's the last one to be destructed will overwrite
302// the output file.
303//
304// To view it in gnuplot, run these two commands:
305//
306// set datafile separator ','
307// plot '<QT_PHYSICS_TIMINGS_FILE>' using 1:2 with lines
308//
309// Security note: This file is trusted since it is opened as write-only
310static const QString qtPhysicsTimingsFile = qEnvironmentVariable("QT_PHYSICS_TIMINGS_FILE");
311
312/////////////////////////////////////////////////////////////////////////////
313
315{
317public:
319 {
320 // Needed to start the frame animation
321 classBegin();
323 }
324};
325
326/////////////////////////////////////////////////////////////////////////////
327
328void QPhysicsWorld::DebugModelHolder::releaseMeshPointer()
329{
330 if (auto base = static_cast<physx::PxBase *>(ptr); base)
331 base->release();
332 ptr = nullptr;
333}
334
335const QVector3D &QPhysicsWorld::DebugModelHolder::halfExtents() const
336{
337 return data;
338}
339void QPhysicsWorld::DebugModelHolder::setHalfExtents(const QVector3D &halfExtents)
340{
341 data = halfExtents;
342}
343float QPhysicsWorld::DebugModelHolder::radius() const
344{
345 return data.x();
346}
347void QPhysicsWorld::DebugModelHolder::setRadius(float radius)
348{
349 data.setX(radius);
350}
351float QPhysicsWorld::DebugModelHolder::heightScale() const
352{
353 return data.x();
354}
355void QPhysicsWorld::DebugModelHolder::setHeightScale(float heightScale)
356{
357 data.setX(heightScale);
358}
359float QPhysicsWorld::DebugModelHolder::halfHeight() const
360{
361 return data.y();
362}
363void QPhysicsWorld::DebugModelHolder::setHalfHeight(float halfHeight)
364{
365 data.setY(halfHeight);
366}
367float QPhysicsWorld::DebugModelHolder::rowScale() const
368{
369 return data.y();
370}
371void QPhysicsWorld::DebugModelHolder::setRowScale(float rowScale)
372{
373 data.setY(rowScale);
374}
375float QPhysicsWorld::DebugModelHolder::columnScale() const
376{
377 return data.z();
378}
379void QPhysicsWorld::DebugModelHolder::setColumnScale(float columnScale)
380{
381 data.setZ(columnScale);
382}
383physx::PxConvexMesh *QPhysicsWorld::DebugModelHolder::getConvexMesh()
384{
385 return static_cast<physx::PxConvexMesh *>(ptr);
386}
387void QPhysicsWorld::DebugModelHolder::setConvexMesh(physx::PxConvexMesh *mesh)
388{
389 ptr = static_cast<void *>(mesh);
390}
391physx::PxTriangleMesh *QPhysicsWorld::DebugModelHolder::getTriangleMesh()
392{
393 return static_cast<physx::PxTriangleMesh *>(ptr);
394}
395void QPhysicsWorld::DebugModelHolder::setTriangleMesh(physx::PxTriangleMesh *mesh)
396{
397 ptr = static_cast<void *>(mesh);
398}
399physx::PxHeightField *QPhysicsWorld::DebugModelHolder::getHeightField()
400{
401 return static_cast<physx::PxHeightField *>(ptr);
402}
403void QPhysicsWorld::DebugModelHolder::setHeightField(physx::PxHeightField *hf)
404{
405 ptr = static_cast<physx::PxHeightField *>(hf);
406}
407
408/////////////////////////////////////////////////////////////////////////////
409
416
417static QWorldManager worldManager = QWorldManager {};
418
419void QPhysicsWorld::registerNode(QAbstractPhysicsNode *physicsNode)
420{
421 auto world = getWorld(physicsNode);
422 if (world) {
423 world->m_newPhysicsNodes.push_back(physicsNode);
424 } else {
425 worldManager.orphanNodes.push_back(physicsNode);
426 }
427}
428
429void QPhysicsWorld::deregisterNode(QAbstractPhysicsNode *physicsNode)
430{
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;
439 }
440 world->m_removedPhysicsNodes.insert(physicsNode);
441 }
442 worldManager.orphanNodes.removeAll(physicsNode);
443}
444
445void QPhysicsWorld::registerJoint(QPhysicsJoint *joint)
446{
447 auto world = getWorld(joint);
448 if (world) {
449 world->m_joints.push_back(joint);
450 } else {
451 worldManager.orphanJoints.push_back(joint);
452 }
453}
454
455void QPhysicsWorld::deregisterJoint(QPhysicsJoint *joint)
456{
457 for (auto world : worldManager.worlds) {
458 QMutexLocker locker(&world->m_removedPhysicsNodesMutex);
459 world->m_removedJoints.insert(joint->getPhysXBackend());
460
461 // Swap erase since order does not matter
462 qsizetype idx = world->m_joints.indexOf(joint);
463 if (idx != -1) {
464 world->m_joints.swapItemsAt(idx, world->m_joints.size() - 1);
465 world->m_joints.pop_back();
466 }
467 }
468 worldManager.orphanJoints.removeAll(joint);
469}
470
471void QPhysicsWorld::registerContact(QAbstractPhysicsNode *sender, QAbstractPhysicsNode *receiver,
472 const QVector<QVector3D> &positions,
473 const QVector<QVector3D> &impulses,
474 const QVector<QVector3D> &normals)
475{
476 // Since collision callbacks happen in the physx simulation thread we need
477 // to store these callbacks. Otherwise, if an object is deleted in the same
478 // frame a 'onBodyContact' signal is enqueued and a crash will happen.
479 // Therefore we save these contact callbacks and run them at the end of the
480 // physics frame when we know if the objects are deleted or not.
481
482 BodyContact contact;
483 contact.sender = sender;
484 contact.receiver = receiver;
485 contact.positions = positions;
486 contact.impulses = impulses;
487 contact.normals = normals;
488
489 m_registeredContacts.push_back(contact);
490}
491
492QPhysicsWorld::QPhysicsWorld(QObject *parent) : QObject(parent)
493{
494 m_inDesignStudio = !qEnvironmentVariableIsEmpty("QML_PUPPET_MODE");
495 m_physx = new QPhysXWorld;
496 m_physx->createWorld();
497
498 worldManager.worlds.push_back(this);
499 matchOrphanNodes();
500 matchOrphanJoints();
501
502 m_frameAnimator = new FrameAnimator;
503 connect(m_frameAnimator, &QQuickFrameAnimation::triggered, this,
504 &QPhysicsWorld::simulateFrame);
505}
506
507QPhysicsWorld::~QPhysicsWorld()
508{
509 if (m_frameAnimator) {
510 m_frameAnimator->stop();
511 delete m_frameAnimator;
512 }
513
514 if (m_physx->scene && m_physx->isRunning && !m_frameFetched)
515 m_physx->scene->fetchResults(true);
516
517 for (auto body : std::as_const(m_physXBodies)) {
518 body->cleanup(m_physx);
519 delete body;
520 }
521 m_physXBodies.clear();
522 m_physx->deleteWorld();
523 delete m_physx;
524 worldManager.worlds.removeAll(this);
525
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';
533 }
534 csvFile.close();
535 } else {
536 qWarning() << "Could not open timings file " << qtPhysicsTimingsFile;
537 }
538 }
539}
540
541void QPhysicsWorld::classBegin() {}
542
543void QPhysicsWorld::componentComplete()
544{
545 if ((!m_running && !m_inDesignStudio) || m_physicsInitialized)
546 return;
547 initPhysics();
548}
549
550QVector3D QPhysicsWorld::gravity() const
551{
552 return m_gravity;
553}
554
555bool QPhysicsWorld::running() const
556{
557 return m_running;
558}
559
560bool QPhysicsWorld::forceDebugDraw() const
561{
562 return m_forceDebugDraw;
563}
564
565bool QPhysicsWorld::enableCCD() const
566{
567 return m_enableCCD;
568}
569
570float QPhysicsWorld::typicalLength() const
571{
572 return m_typicalLength;
573}
574
575float QPhysicsWorld::typicalSpeed() const
576{
577 return m_typicalSpeed;
578}
579
580bool QPhysicsWorld::isNodeRemoved(QAbstractPhysicsNode *object)
581{
582 return m_removedPhysicsNodes.contains(object);
583}
584
585void QPhysicsWorld::setGravity(QVector3D gravity)
586{
587 if (m_gravity == gravity)
588 return;
589
590 m_gravity = gravity;
591 if (m_physx->scene) {
592 m_physx->scene->setGravity(QPhysicsUtils::toPhysXType(m_gravity));
593 }
594 emit gravityChanged(m_gravity);
595}
596
597void QPhysicsWorld::setRunning(bool running)
598{
599 if (m_running == running)
600 return;
601
602 m_running = running;
603 if (!m_inDesignStudio && m_running && !m_physicsInitialized)
604 initPhysics();
605
606 if (running)
607 m_frameAnimator->start();
608 else
609 m_frameAnimator->stop();
610
611 emit runningChanged(m_running);
612}
613
614void QPhysicsWorld::setForceDebugDraw(bool forceDebugDraw)
615{
616 if (m_forceDebugDraw == forceDebugDraw)
617 return;
618
619 m_forceDebugDraw = forceDebugDraw;
620 if (!m_forceDebugDraw)
621 disableDebugDraw();
622 else
623 updateDebugDraw();
624 emit forceDebugDrawChanged(m_forceDebugDraw);
625}
626
627QQuick3DNode *QPhysicsWorld::viewport() const
628{
629 return m_viewport;
630}
631
632void QPhysicsWorld::setHasIndividualDebugDraw()
633{
634 m_hasIndividualDebugDraw = true;
635}
636
637void QPhysicsWorld::setViewport(QQuick3DNode *viewport)
638{
639 if (m_viewport == viewport)
640 return;
641
642 m_viewport = viewport;
643
644 // TODO: test this
645 for (auto material : std::as_const(m_debugMaterials))
646 delete material;
647 m_debugMaterials.clear();
648
649 for (auto &holder : m_collisionShapeDebugModels) {
650 holder.releaseMeshPointer();
651 delete holder.model;
652 }
653 m_collisionShapeDebugModels.clear();
654
655 emit viewportChanged(m_viewport);
656}
657
658void QPhysicsWorld::setMinimumTimestep(float minTimestep)
659{
660 if (qFuzzyCompare(m_minTimestep, minTimestep))
661 return;
662
663 if (minTimestep > m_maxTimestep) {
664 qWarning("Minimum timestep greater than maximum timestep, value clamped");
665 minTimestep = qMin(minTimestep, m_maxTimestep);
666 }
667
668 if (minTimestep < 0.f) {
669 qWarning("Minimum timestep less than zero, value clamped");
670 minTimestep = qMax(minTimestep, 0.f);
671 }
672
673 if (qFuzzyCompare(m_minTimestep, minTimestep))
674 return;
675
676 m_minTimestep = minTimestep;
677 emit minimumTimestepChanged(m_minTimestep);
678}
679
680void QPhysicsWorld::setMaximumTimestep(float maxTimestep)
681{
682 if (qFuzzyCompare(m_maxTimestep, maxTimestep))
683 return;
684
685 if (maxTimestep < 0.f) {
686 qWarning("Maximum timestep less than zero, value clamped");
687 maxTimestep = qMax(maxTimestep, 0.f);
688 }
689
690 if (qFuzzyCompare(m_maxTimestep, maxTimestep))
691 return;
692
693 m_maxTimestep = maxTimestep;
694 emit maximumTimestepChanged(maxTimestep);
695}
696
697void QPhysicsWorld::setupDebugMaterials(QQuick3DNode *sceneNode)
698{
699 if (!m_debugMaterials.isEmpty())
700 return;
701
702 const int lineWidth = m_inDesignStudio ? 1 : 3;
703
704 // These colors match the indices of DebugDrawBodyType enum
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);
716 }
717}
718
719void QPhysicsWorld::updateDebugDraw()
720{
721 if (!(m_forceDebugDraw || m_hasIndividualDebugDraw)) {
722 // Nothing to draw, trash all previous models (if any) and return
723 for (auto &holder : m_collisionShapeDebugModels) {
724 holder.releaseMeshPointer();
725 delete holder.model;
726 }
727 m_collisionShapeDebugModels.clear();
728 return;
729 }
730
731 // Use scene node if no viewport has been specified
732 auto sceneNode = m_viewport ? m_viewport : m_scene;
733
734 if (sceneNode == nullptr)
735 return;
736
737 setupDebugMaterials(sceneNode);
738 m_hasIndividualDebugDraw = false;
739
740 // Store the collision shapes we have now so we can clear out the removed ones
741 QSet<QPair<QAbstractCollisionShape *, QAbstractPhysXNode *>> currentCollisionShapes;
742 currentCollisionShapes.reserve(m_collisionShapeDebugModels.size());
743
744 for (QAbstractPhysXNode *node : std::as_const(m_physXBodies)) {
745 if (!node->debugGeometryCapability())
746 continue;
747
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];
753
754 if (!m_forceDebugDraw && !collisionShape->enableDebugDraw())
755 continue;
756
757 DebugModelHolder &holder =
758 m_collisionShapeDebugModels[std::make_pair(collisionShape, node)];
759 auto &model = holder.model;
760
761 currentCollisionShapes.insert(std::make_pair(collisionShape, node));
762
763 m_hasIndividualDebugDraw =
764 m_hasIndividualDebugDraw || collisionShape->enableDebugDraw();
765
766 // Create/Update debug view infrastructure
767 if (!model) {
768 model = new QQuick3DModel();
769 model->setParentItem(sceneNode);
770 model->setParent(sceneNode);
771 model->setCastsShadows(false);
772 model->setReceivesShadows(false);
773 model->setCastsReflections(false);
774 }
775
776 model->setVisible(true);
777
778 { // update or set material
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);
784 }
785 }
786
787 // Special handling of CharacterController since it has collision shapes,
788 // but not PhysX shapes
789 if (qobject_cast<QCharacterController *>(node->frontendNode)) {
790 QCapsuleShape *capsuleShape = qobject_cast<QCapsuleShape *>(collisionShape);
791 if (!capsuleShape)
792 continue;
793
794 const float radius = capsuleShape->diameter() * 0.5;
795 const float halfHeight = capsuleShape->height() * 0.5;
796
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);
804 }
805
806 model->setPosition(node->frontendNode->scenePosition());
807 model->setRotation(node->frontendNode->sceneRotation()
808 * QQuaternion::fromEulerAngles(0, 0, 90));
809 continue;
810 }
811
812 if (node->shapes.length() < length)
813 continue;
814
815 const auto physXShape = node->shapes[idx];
816 auto localPose = physXShape->getLocalPose();
817
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);
829 }
830
831 }
832 break;
833
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);
843 }
844 }
845 break;
846
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();
852
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);
861 }
862 }
863 break;
864
865 case physx::PxGeometryType::ePLANE:{
866 physx::PxPlaneGeometry planeGeometry;
867 physXShape->getPlaneGeometry(planeGeometry);
868 // Special rotation
869 const QQuaternion rotation =
870 QPhysicsUtils::kMinus90YawRotation * QPhysicsUtils::toQtType(localPose.q);
871 localPose = physx::PxTransform(localPose.p, QPhysicsUtils::toPhysXType(rotation));
872
873 if (model->geometry() == nullptr) {
874 auto geom = QDebugDrawHelper::generatePlaneGeometry();
875 geom->setParent(model);
876 model->setGeometry(geom);
877 }
878 }
879 break;
880
881 // For heightfield, convex mesh and triangle mesh we increase its reference count
882 // to make sure it does not get dereferenced and deleted so that the new mesh will
883 // have another memory address so we know when it has changed.
884 case physx::PxGeometryType::eHEIGHTFIELD: {
885 physx::PxHeightFieldGeometry heightFieldGeometry;
886 bool success = physXShape->getHeightFieldGeometry(heightFieldGeometry);
887 Q_ASSERT(success);
888 const float heightScale = holder.heightScale();
889 const float rowScale = holder.rowScale();
890 const float columnScale = holder.columnScale();
891
892 if (auto heightField = holder.getHeightField();
893 heightField && heightField != heightFieldGeometry.heightField) {
894 heightField->release();
895 holder.setHeightField(nullptr);
896 }
897
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);
905 }
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);
914 }
915 }
916 break;
917
918 case physx::PxGeometryType::eCONVEXMESH: {
919 physx::PxConvexMeshGeometry convexMeshGeometry;
920 const bool success = physXShape->getConvexMeshGeometry(convexMeshGeometry);
921 Q_ASSERT(success);
922 const auto rotation = convexMeshGeometry.scale.rotation * localPose.q;
923 localPose = physx::PxTransform(localPose.p, rotation);
924 model->setScale(QPhysicsUtils::toQtType(convexMeshGeometry.scale.scale));
925
926 if (auto convexMesh = holder.getConvexMesh();
927 convexMesh && convexMesh != convexMeshGeometry.convexMesh) {
928 convexMesh->release();
929 holder.setConvexMesh(nullptr);
930 }
931
932 if (!model->geometry() || !holder.getConvexMesh()) {
933 if (!holder.getConvexMesh()) {
934 convexMeshGeometry.convexMesh->acquireReference();
935 holder.setConvexMesh(convexMeshGeometry.convexMesh);
936 }
937 auto geom = QDebugDrawHelper::generateConvexMeshGeometry(
938 convexMeshGeometry.convexMesh);
939 geom->setParent(model);
940 model->setGeometry(geom);
941 }
942 }
943 break;
944
945 case physx::PxGeometryType::eTRIANGLEMESH: {
946 physx::PxTriangleMeshGeometry triangleMeshGeometry;
947 const bool success = physXShape->getTriangleMeshGeometry(triangleMeshGeometry);
948 Q_ASSERT(success);
949 const auto rotation = triangleMeshGeometry.scale.rotation * localPose.q;
950 localPose = physx::PxTransform(localPose.p, rotation);
951 model->setScale(QPhysicsUtils::toQtType(triangleMeshGeometry.scale.scale));
952
953 if (auto triangleMesh = holder.getTriangleMesh();
954 triangleMesh && triangleMesh != triangleMeshGeometry.triangleMesh) {
955 triangleMesh->release();
956 holder.setTriangleMesh(nullptr);
957 }
958
959 if (!model->geometry() || !holder.getTriangleMesh()) {
960 if (!holder.getTriangleMesh()) {
961 triangleMeshGeometry.triangleMesh->acquireReference();
962 holder.setTriangleMesh(triangleMeshGeometry.triangleMesh);
963 }
964 auto geom = QDebugDrawHelper::generateTriangleMeshGeometry(
965 triangleMeshGeometry.triangleMesh);
966 geom->setParent(model);
967 model->setGeometry(geom);
968 }
969 }
970 break;
971
972 case physx::PxGeometryType::eINVALID:
973 case physx::PxGeometryType::eGEOMETRY_COUNT:
974 // should not happen
975 Q_UNREACHABLE();
976 }
977
978 auto globalPose = node->getGlobalPose();
979 auto finalPose = globalPose.transform(localPose);
980
981 model->setRotation(QPhysicsUtils::toQtType(finalPose.q));
982 model->setPosition(QPhysicsUtils::toQtType(finalPose.p));
983 }
984 }
985
986 // Remove old collision shapes
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();
993 if (holder.model)
994 delete holder.model;
995 return true;
996 }
997 return false;
998 });
999}
1000
1001static void collectPhysicsNodes(QQuick3DObject *node, QList<QAbstractPhysicsNode *> &nodes)
1002{
1003 if (auto shape = qobject_cast<QAbstractPhysicsNode *>(node)) {
1004 nodes.push_back(shape);
1005 return;
1006 }
1007
1008 auto childItems = node->childItems();
1009 for (QQuick3DObject *child : std::as_const(childItems))
1010 collectPhysicsNodes(child, nodes);
1011}
1012
1013void QPhysicsWorld::updateDebugDrawDesignStudio()
1014{
1015 // Use scene node if no viewport has been specified
1016 auto sceneNode = m_viewport ? m_viewport : m_scene;
1017
1018 if (sceneNode == nullptr)
1019 return;
1020
1021 setupDebugMaterials(sceneNode);
1022
1023 // Store the collision shapes we have now so we can clear out the removed ones
1024 QSet<QPair<QAbstractCollisionShape *, QAbstractPhysicsNode *>> currentCollisionShapes;
1025 currentCollisionShapes.reserve(m_collisionShapeDebugModels.size());
1026
1027 QList<QAbstractPhysicsNode *> activePhysicsNodes;
1028 activePhysicsNodes.reserve(m_collisionShapeDebugModels.size());
1029 collectPhysicsNodes(m_scene, activePhysicsNodes);
1030
1031 for (QAbstractPhysicsNode *node : std::as_const(activePhysicsNodes)) {
1032
1033 const auto &collisionShapes = node->getCollisionShapesList();
1034 const int materialIdx = 0; // Just take first material
1035 const int length = collisionShapes.length();
1036
1037 const bool isCharacterController = qobject_cast<QCharacterController *>(node) != nullptr;
1038
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;
1044
1045 currentCollisionShapes.insert(std::make_pair(collisionShape, node));
1046
1047 m_hasIndividualDebugDraw =
1048 m_hasIndividualDebugDraw || collisionShape->enableDebugDraw();
1049
1050 // Create/Update debug view infrastructure
1051 {
1052 // Hack: we have to delete the model every frame so it shows up in QDS
1053 // whenever the code is updated, not sure why ¯\_(?)_/¯
1054 delete model;
1055 model = new QQuick3DModel();
1056 model->setParentItem(sceneNode);
1057 model->setParent(sceneNode);
1058 model->setCastsShadows(false);
1059 model->setReceivesShadows(false);
1060 model->setCastsReflections(false);
1061 }
1062
1063 const bool hasGeometry = holder.geometry != nullptr;
1064 QVector3D scenePosition = collisionShape->scenePosition();
1065 QQuaternion sceneRotation = collisionShape->sceneRotation();
1066 QQuick3DGeometry *newGeometry = nullptr;
1067
1068 if (isCharacterController)
1069 sceneRotation = sceneRotation * QQuaternion::fromEulerAngles(QVector3D(0, 0, 90));
1070
1071 { // update or set material
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);
1077 }
1078 }
1079
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);
1086 }
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);
1093 }
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;
1099
1100 if ((!qFuzzyCompare(radiusOld, radius) || !qFuzzyCompare(halfHeightOld, halfHeight))
1101 || !hasGeometry) {
1102 newGeometry = QDebugDrawHelper::generateCapsuleGeometry(radius, halfHeight);
1103 holder.setRadius(radius);
1104 holder.setHalfHeight(halfHeight);
1105 }
1106 } else if (qobject_cast<QPlaneShape *>(collisionShape)) {
1107 if (!hasGeometry)
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)
1121 || !hasGeometry) {
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);
1128 }
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";
1134 } else {
1135 model->setScale(QPhysicsUtils::toQtType(convexMeshGeometry->scale.scale));
1136
1137 if (!hasGeometry) {
1138 newGeometry = QDebugDrawHelper::generateConvexMeshGeometry(
1139 convexMeshGeometry->convexMesh);
1140 }
1141 }
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";
1147 } else {
1148 model->setScale(QPhysicsUtils::toQtType(triangleMeshGeometry->scale.scale));
1149
1150 if (!hasGeometry) {
1151 newGeometry = QDebugDrawHelper::generateTriangleMeshGeometry(
1152 triangleMeshGeometry->triangleMesh);
1153 }
1154 }
1155 }
1156
1157 if (newGeometry) {
1158 delete holder.geometry;
1159 holder.geometry = newGeometry;
1160 }
1161
1162 model->setGeometry(holder.geometry);
1163 model->setVisible(true);
1164
1165 model->setRotation(sceneRotation);
1166 model->setPosition(scenePosition);
1167 }
1168 }
1169
1170 // Remove old debug models
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();
1177 if (holder.model) {
1178 delete holder.geometry;
1179 delete holder.model;
1180 }
1181 return true;
1182 }
1183 return false;
1184 });
1185}
1186
1187void QPhysicsWorld::disableDebugDraw()
1188{
1189 m_hasIndividualDebugDraw = false;
1190
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;
1198 return;
1199 }
1200 }
1201 }
1202}
1203
1204void QPhysicsWorld::setEnableCCD(bool enableCCD)
1205{
1206 if (m_enableCCD == enableCCD)
1207 return;
1208
1209 if (m_physicsInitialized) {
1210 qWarning()
1211 << "Warning: Changing 'enableCCD' after physics is initialized will have no effect";
1212 return;
1213 }
1214
1215 m_enableCCD = enableCCD;
1216 emit enableCCDChanged(m_enableCCD);
1217}
1218
1219void QPhysicsWorld::setTypicalLength(float typicalLength)
1220{
1221 if (qFuzzyCompare(typicalLength, m_typicalLength))
1222 return;
1223
1224 if (typicalLength <= 0.f) {
1225 qWarning() << "Warning: 'typicalLength' value less than zero, ignored";
1226 return;
1227 }
1228
1229 if (m_physicsInitialized) {
1230 qWarning() << "Warning: Changing 'typicalLength' after physics is initialized will have "
1231 "no effect";
1232 return;
1233 }
1234
1235 m_typicalLength = typicalLength;
1236
1237 emit typicalLengthChanged(typicalLength);
1238}
1239
1240void QPhysicsWorld::setTypicalSpeed(float typicalSpeed)
1241{
1242 if (qFuzzyCompare(typicalSpeed, m_typicalSpeed))
1243 return;
1244
1245 if (m_physicsInitialized) {
1246 qWarning() << "Warning: Changing 'typicalSpeed' after physics is initialized will have "
1247 "no effect";
1248 return;
1249 }
1250
1251 m_typicalSpeed = typicalSpeed;
1252
1253 emit typicalSpeedChanged(typicalSpeed);
1254}
1255
1256float QPhysicsWorld::defaultDensity() const
1257{
1258 return m_defaultDensity;
1259}
1260
1261float QPhysicsWorld::minimumTimestep() const
1262{
1263 return m_minTimestep;
1264}
1265
1266float QPhysicsWorld::maximumTimestep() const
1267{
1268 return m_maxTimestep;
1269}
1270
1271void QPhysicsWorld::setDefaultDensity(float defaultDensity)
1272{
1273 if (qFuzzyCompare(m_defaultDensity, defaultDensity))
1274 return;
1275 m_defaultDensity = defaultDensity;
1276
1277 // Go through all dynamic rigid bodies and update the default density
1278 for (QAbstractPhysXNode *body : std::as_const(m_physXBodies))
1279 body->updateDefaultDensity(m_defaultDensity);
1280
1281 emit defaultDensityChanged(defaultDensity);
1282}
1283
1284// Remove physics world items that no longer exist
1285
1286void QPhysicsWorld::cleanupRemovedNodes()
1287{
1288 m_physXBodies.removeIf([this](QAbstractPhysXNode *body) {
1289 return body->cleanupIfRemoved(m_physx);
1290 });
1291 m_removedPhysicsNodes.clear();
1292}
1293
1294void QPhysicsWorld::cleanupRemovedJoints()
1295{
1296 for (physx::PxJoint *joint : m_removedJoints) {
1297 if (joint)
1298 joint->release();
1299 }
1300 m_removedJoints.clear();
1301}
1302
1303void QPhysicsWorld::initPhysics()
1304{
1305 Q_ASSERT(!m_physicsInitialized);
1306
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;
1311}
1312
1313void QPhysicsWorld::simulateFrame()
1314{
1315 constexpr double MILLIONTH = 0.000001;
1316 constexpr double THOUSANDTH = 0.001;
1317
1318 if (m_inDesignStudio) {
1319 frameFinishedDesignStudio();
1320 return;
1321 }
1322
1323 if (!m_physx->isRunning) {
1324 m_timer.start();
1325 m_physx->isRunning = true;
1326 const double minTimestepSecs = m_minTimestep * 0.001;
1327 m_physx->scene->simulate(minTimestepSecs);
1328 m_currTimeStep = minTimestepSecs;
1329 return;
1330 }
1331
1332 // Frame not ready yet
1333 if (!m_frameFetched && !m_physx->scene->checkResults()) {
1334 return;
1335 }
1336
1337 // Frame ready, fetch and finish it
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);
1345 }
1346 }
1347
1348 // Assuming: 0 <= minTimestep <= maxTimestep
1349 const double deltaMS = m_timer.nsecsElapsed() * MILLIONTH;
1350 if (deltaMS < m_minTimestep)
1351 return;
1352 const double deltaSecs = qMin<double>(deltaMS, m_maxTimestep) * THOUSANDTH;
1353 m_timer.restart();
1354 m_physx->scene->simulate(deltaSecs);
1355 m_frameFetched = false;
1356 m_currTimeStep = deltaSecs;
1357}
1358
1359void QPhysicsWorld::frameFinished(float deltaTime)
1360{
1361 matchOrphanNodes();
1362 matchOrphanJoints();
1363 emitContactCallbacks();
1364 cleanupRemovedNodes();
1365 cleanupRemovedJoints();
1366
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);
1371 }
1372 m_newPhysicsNodes.clear();
1373
1374 QHash<QQuick3DNode *, QMatrix4x4> transformCache;
1375
1376 // TODO: Use dirty flag/dirty list to avoid redoing things that didn't change
1377 for (auto *physXBody : std::as_const(m_physXBodies)) {
1378 physXBody->markDirtyShapes();
1379 physXBody->rebuildDirtyShapes(this, m_physx);
1380 physXBody->updateFilters();
1381
1382 // Sync the physics world and the scene
1383 physXBody->sync(deltaTime, transformCache);
1384 }
1385
1386 for (QPhysicsJoint *joint : std::as_const(m_joints)) {
1387 joint->updatePhysXBackend();
1388 }
1389
1390 updateDebugDraw();
1391 emit frameDone(deltaTime * 1000);
1392}
1393
1394void QPhysicsWorld::frameFinishedDesignStudio()
1395{
1396 // Note sure if this is needed but do it anyway
1397 matchOrphanNodes();
1398 matchOrphanJoints();
1399 emitContactCallbacks();
1400 cleanupRemovedNodes();
1401 // Ignore new physics nodes, we find them from the scene node anyway
1402 m_newPhysicsNodes.clear();
1403
1404 updateDebugDrawDesignStudio();
1405}
1406
1407QPhysicsWorld *QPhysicsWorld::getWorld(QQuick3DNode *node)
1408{
1409 for (QPhysicsWorld *world : std::as_const(worldManager.worlds)) {
1410 if (!world->m_scene) {
1411 continue;
1412 }
1413
1414 QQuick3DNode *nodeCurr = node;
1415
1416 // Maybe pointless but check starting node
1417 if (nodeCurr == world->m_scene)
1418 return world;
1419
1420 while (nodeCurr->parentNode()) {
1421 nodeCurr = nodeCurr->parentNode();
1422 if (nodeCurr == world->m_scene)
1423 return world;
1424 }
1425 }
1426
1427 return nullptr;
1428}
1429
1430QPhysicsWorld *QPhysicsWorld::getWorld(QPhysicsJoint *joint)
1431{
1432 for (QPhysicsWorld *world : worldManager.worlds) {
1433 if (!world->m_scene) {
1434 continue;
1435 }
1436
1437 QObject *nodeCurr = joint;
1438
1439 while (nodeCurr->parent()) {
1440 nodeCurr = nodeCurr->parent();
1441 if (nodeCurr == world->m_scene) {
1442 return world;
1443 } else if (auto view3d = qobject_cast<QQuick3DViewport *>(nodeCurr);
1444 view3d && view3d->scene() == world->m_scene) {
1445 // HACK? if the parent is a view3d, check against its "implicit" scene
1446 return world;
1447 }
1448 }
1449 }
1450
1451 return nullptr;
1452}
1453
1454void QPhysicsWorld::matchOrphanNodes()
1455{
1456 // FIXME: does this need thread safety?
1457 if (worldManager.orphanNodes.isEmpty())
1458 return;
1459
1460 qsizetype numNodes = worldManager.orphanNodes.length();
1461 qsizetype idx = 0;
1462
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);
1468 // swap-erase
1469 worldManager.orphanNodes.swapItemsAt(idx, numNodes - 1);
1470 worldManager.orphanNodes.pop_back();
1471 numNodes--;
1472 } else {
1473 idx++;
1474 }
1475 }
1476}
1477
1478void QPhysicsWorld::matchOrphanJoints()
1479{
1480 if (worldManager.orphanJoints.isEmpty())
1481 return;
1482
1483 qsizetype numNodes = worldManager.orphanJoints.length();
1484 qsizetype idx = 0;
1485
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);
1491 // swap-erase
1492 worldManager.orphanJoints.swapItemsAt(idx, numNodes - 1);
1493 worldManager.orphanJoints.pop_back();
1494 numNodes--;
1495 } else {
1496 idx++;
1497 }
1498 }
1499}
1500
1501void QPhysicsWorld::findPhysicsNodes()
1502{
1503 // This method finds the physics nodes inside the scene pointed to by the
1504 // scene property. This method is necessary to run whenever the scene
1505 // property is changed.
1506 if (m_scene == nullptr)
1507 return;
1508
1509 // Recursively go through all children and add all QAbstractPhysicsNode's
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) {
1514 // This should never happen but check anyway.
1515 if (converted->m_backendObject != nullptr) {
1516 qWarning() << "Warning: physics node already associated with a backend node.";
1517 continue;
1518 }
1519
1520 m_newPhysicsNodes.push_back(converted);
1521 worldManager.orphanNodes.removeAll(converted); // No longer orphan
1522 }
1523 children.append(child->childItems());
1524 }
1525}
1526
1527void QPhysicsWorld::emitContactCallbacks()
1528{
1529 for (const QPhysicsWorld::BodyContact &contact : std::as_const(m_registeredContacts)) {
1530 if (m_removedPhysicsNodes.contains(contact.sender)
1531 || m_removedPhysicsNodes.contains(contact.receiver))
1532 continue;
1533 contact.receiver->registerContact(contact.sender, contact.positions, contact.impulses,
1534 contact.normals);
1535 }
1536
1537 m_registeredContacts.clear();
1538}
1539
1540physx::PxPhysics *QPhysicsWorld::getPhysics()
1541{
1542 return StaticPhysXObjects::getReference().physics;
1543}
1544
1545physx::PxCooking *QPhysicsWorld::getCooking()
1546{
1547 return StaticPhysXObjects::getReference().cooking;
1548}
1549
1550physx::PxControllerManager *QPhysicsWorld::controllerManager()
1551{
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;
1555 }
1556 return m_physx->controllerManager;
1557}
1558
1559QQuick3DNode *QPhysicsWorld::scene() const
1560{
1561 return m_scene;
1562}
1563
1564void QPhysicsWorld::setScene(QQuick3DNode *newScene)
1565{
1566 if (m_scene == newScene)
1567 return;
1568
1569 m_scene = newScene;
1570
1571 // Delete all nodes since they are associated with the previous scene
1572 for (auto body : std::as_const(m_physXBodies)) {
1573 deregisterNode(body->frontendNode);
1574 }
1575
1576 // Check if scene is already used by another world
1577 bool sceneOK = true;
1578 for (QPhysicsWorld *world : std::as_const(worldManager.worlds)) {
1579 if (world != this && world->scene() == newScene) {
1580 sceneOK = false;
1581 qWarning() << "Warning: scene already associated with physics world";
1582 }
1583 }
1584
1585 if (sceneOK)
1586 findPhysicsNodes();
1587 emit sceneChanged();
1588}
1589
1590int QPhysicsWorld::numThreads() const
1591{
1592 return m_numThreads;
1593}
1594
1595void QPhysicsWorld::setNumThreads(int newNumThreads)
1596{
1597 if (m_numThreads == newNumThreads)
1598 return;
1599 m_numThreads = newNumThreads;
1600 emit numThreadsChanged();
1601}
1602
1603bool QPhysicsWorld::reportKinematicKinematicCollisions() const
1604{
1605 return m_reportKinematicKinematicCollisions;
1606}
1607
1608void QPhysicsWorld::setReportKinematicKinematicCollisions(
1609 bool newReportKinematicKinematicCollisions)
1610{
1611 if (m_reportKinematicKinematicCollisions == newReportKinematicKinematicCollisions)
1612 return;
1613 m_reportKinematicKinematicCollisions = newReportKinematicKinematicCollisions;
1614 emit reportKinematicKinematicCollisionsChanged();
1615}
1616
1617bool QPhysicsWorld::reportStaticKinematicCollisions() const
1618{
1619 return m_reportStaticKinematicCollisions;
1620}
1621
1622void QPhysicsWorld::setReportStaticKinematicCollisions(bool newReportStaticKinematicCollisions)
1623{
1624 if (m_reportStaticKinematicCollisions == newReportStaticKinematicCollisions)
1625 return;
1626 m_reportStaticKinematicCollisions = newReportStaticKinematicCollisions;
1627 emit reportStaticKinematicCollisionsChanged();
1628}
1629
1630QPhysicsWorld::QueryStructure QPhysicsWorld::staticQueryStructure() const
1631{
1632 return m_staticQueryStructure;
1633}
1634
1635void QPhysicsWorld::setStaticQueryStructure(QueryStructure newStaticQueryStructure)
1636{
1637 if (m_staticQueryStructure == newStaticQueryStructure)
1638 return;
1639
1640 if (newStaticQueryStructure == QueryStructure::NoStructure) {
1641 qWarning()
1642 << "Warning: The QueryStructure::NoStructure is not supported for staticQueryStructure. Available options: [StaticTree, DynamicTree]";
1643 return;
1644 }
1645
1646 if (m_physicsInitialized) {
1647 qWarning()
1648 << "Warning: Changing 'staticQueryStructure' after physics is initialized will have no effect";
1649 return;
1650 }
1651
1652 m_staticQueryStructure = newStaticQueryStructure;
1653 emit staticQueryStructureChanged();
1654}
1655
1656QPhysicsWorld::QueryStructure QPhysicsWorld::dynamicQueryStructure() const
1657{
1658 return m_dynamicQueryStructure;
1659}
1660
1661void QPhysicsWorld::setDynamicQueryStructure(QueryStructure newDynamicQueryStructure)
1662{
1663 if (m_dynamicQueryStructure == newDynamicQueryStructure)
1664 return;
1665
1666 if (m_physicsInitialized) {
1667 qWarning()
1668 << "Warning: Changing 'dynamicQueryStructure' after physics is initialized will have no effect";
1669 return;
1670 }
1671
1672 m_dynamicQueryStructure = newDynamicQueryStructure;
1673 emit dynamicQueryStructureChanged();
1674}
1675
1677
1678#include "qphysicsworld.moc"
Definition qlist.h:82
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
#define Q_OBJECT
#define emit
QVector< QAbstractPhysicsNode * > orphanNodes
QVector< QPhysicsWorld * > worlds
QVector< QPhysicsJoint * > orphanJoints