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 : std::as_const(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 : std::as_const(m_physXBodies)) {
419 body->cleanup(m_physx);
420 delete body;
421 }
422 m_physXBodies.clear();
423 m_physx->deleteWorld();
424 delete m_physx;
425 worldManager.worlds.removeAll(this);
426
427 if (!qtPhysicsTimingsFile.isEmpty()) {
428 if (m_frameTimings.isEmpty()) {
429 qWarning() << "No frame timings saved.";
430 } else if (auto csvFile = QFile(qtPhysicsTimingsFile); csvFile.open(QIODevice::WriteOnly)) {
431 QTextStream out(&csvFile);
432 for (int i = 1; i < m_frameTimings.size(); i++) {
433 out << i << "," << m_frameTimings[i] << '\n';
434 }
435 csvFile.close();
436 } else {
437 qWarning() << "Could not open timings file " << qtPhysicsTimingsFile;
438 }
439 }
440}
441
442void QPhysicsWorld::classBegin() {}
443
444void QPhysicsWorld::componentComplete()
445{
446 if ((!m_running && !m_inDesignStudio) || m_physicsInitialized)
447 return;
448 initPhysics();
449}
450
451QVector3D QPhysicsWorld::gravity() const
452{
453 return m_gravity;
454}
455
456bool QPhysicsWorld::running() const
457{
458 return m_running;
459}
460
461bool QPhysicsWorld::forceDebugDraw() const
462{
463 return m_forceDebugDraw;
464}
465
466bool QPhysicsWorld::enableCCD() const
467{
468 return m_enableCCD;
469}
470
471float QPhysicsWorld::typicalLength() const
472{
473 return m_typicalLength;
474}
475
476float QPhysicsWorld::typicalSpeed() const
477{
478 return m_typicalSpeed;
479}
480
481bool QPhysicsWorld::isNodeRemoved(QAbstractPhysicsNode *object)
482{
483 return m_removedPhysicsNodes.contains(object);
484}
485
486void QPhysicsWorld::setGravity(QVector3D gravity)
487{
488 if (m_gravity == gravity)
489 return;
490
491 m_gravity = gravity;
492 if (m_physx->scene) {
493 m_physx->scene->setGravity(QPhysicsUtils::toPhysXType(m_gravity));
494 }
495 emit gravityChanged(m_gravity);
496}
497
498void QPhysicsWorld::setRunning(bool running)
499{
500 if (m_running == running)
501 return;
502
503 m_running = running;
504 if (!m_inDesignStudio && m_running && !m_physicsInitialized)
505 initPhysics();
506
507 if (running)
508 m_frameAnimator->start();
509 else
510 m_frameAnimator->stop();
511
512 emit runningChanged(m_running);
513}
514
515void QPhysicsWorld::setForceDebugDraw(bool forceDebugDraw)
516{
517 if (m_forceDebugDraw == forceDebugDraw)
518 return;
519
520 m_forceDebugDraw = forceDebugDraw;
521 if (!m_forceDebugDraw)
522 disableDebugDraw();
523 else
524 updateDebugDraw();
525 emit forceDebugDrawChanged(m_forceDebugDraw);
526}
527
528QQuick3DNode *QPhysicsWorld::viewport() const
529{
530 return m_viewport;
531}
532
533void QPhysicsWorld::setHasIndividualDebugDraw()
534{
535 m_hasIndividualDebugDraw = true;
536}
537
538void QPhysicsWorld::setViewport(QQuick3DNode *viewport)
539{
540 if (m_viewport == viewport)
541 return;
542
543 m_viewport = viewport;
544
545 // TODO: test this
546 for (auto material : std::as_const(m_debugMaterials))
547 delete material;
548 m_debugMaterials.clear();
549
550 for (auto &holder : m_collisionShapeDebugModels) {
551 holder.releaseMeshPointer();
552 delete holder.model;
553 }
554 m_collisionShapeDebugModels.clear();
555
556 emit viewportChanged(m_viewport);
557}
558
559void QPhysicsWorld::setMinimumTimestep(float minTimestep)
560{
561 if (qFuzzyCompare(m_minTimestep, minTimestep))
562 return;
563
564 if (minTimestep > m_maxTimestep) {
565 qWarning("Minimum timestep greater than maximum timestep, value clamped");
566 minTimestep = qMin(minTimestep, m_maxTimestep);
567 }
568
569 if (minTimestep < 0.f) {
570 qWarning("Minimum timestep less than zero, value clamped");
571 minTimestep = qMax(minTimestep, 0.f);
572 }
573
574 if (qFuzzyCompare(m_minTimestep, minTimestep))
575 return;
576
577 m_minTimestep = minTimestep;
578 emit minimumTimestepChanged(m_minTimestep);
579}
580
581void QPhysicsWorld::setMaximumTimestep(float maxTimestep)
582{
583 if (qFuzzyCompare(m_maxTimestep, maxTimestep))
584 return;
585
586 if (maxTimestep < 0.f) {
587 qWarning("Maximum timestep less than zero, value clamped");
588 maxTimestep = qMax(maxTimestep, 0.f);
589 }
590
591 if (qFuzzyCompare(m_maxTimestep, maxTimestep))
592 return;
593
594 m_maxTimestep = maxTimestep;
595 emit maximumTimestepChanged(maxTimestep);
596}
597
598void QPhysicsWorld::setupDebugMaterials(QQuick3DNode *sceneNode)
599{
600 if (!m_debugMaterials.isEmpty())
601 return;
602
603 const int lineWidth = m_inDesignStudio ? 1 : 3;
604
605 // These colors match the indices of DebugDrawBodyType enum
606 for (auto color : { QColorConstants::Svg::chartreuse, QColorConstants::Svg::cyan,
607 QColorConstants::Svg::lightsalmon, QColorConstants::Svg::red,
608 QColorConstants::Svg::blueviolet, QColorConstants::Svg::black }) {
609 auto debugMaterial = new QQuick3DPrincipledMaterial();
610 debugMaterial->setLineWidth(lineWidth);
611 debugMaterial->setParentItem(sceneNode);
612 debugMaterial->setParent(sceneNode);
613 debugMaterial->setBaseColor(color);
614 debugMaterial->setLighting(QQuick3DPrincipledMaterial::NoLighting);
615 debugMaterial->setCullMode(QQuick3DMaterial::NoCulling);
616 m_debugMaterials.push_back(debugMaterial);
617 }
618}
619
620void QPhysicsWorld::updateDebugDraw()
621{
622 if (!(m_forceDebugDraw || m_hasIndividualDebugDraw)) {
623 // Nothing to draw, trash all previous models (if any) and return
624 for (auto &holder : m_collisionShapeDebugModels) {
625 holder.releaseMeshPointer();
626 delete holder.model;
627 }
628 m_collisionShapeDebugModels.clear();
629 return;
630 }
631
632 // Use scene node if no viewport has been specified
633 auto sceneNode = m_viewport ? m_viewport : m_scene;
634
635 if (sceneNode == nullptr)
636 return;
637
638 setupDebugMaterials(sceneNode);
639 m_hasIndividualDebugDraw = false;
640
641 // Store the collision shapes we have now so we can clear out the removed ones
642 QSet<QPair<QAbstractCollisionShape *, QAbstractPhysXNode *>> currentCollisionShapes;
643 currentCollisionShapes.reserve(m_collisionShapeDebugModels.size());
644
645 for (QAbstractPhysXNode *node : std::as_const(m_physXBodies)) {
646 if (!node->debugGeometryCapability())
647 continue;
648
649 const auto &collisionShapes = node->frontendNode->getCollisionShapesList();
650 const int materialIdx = static_cast<int>(node->getDebugDrawBodyType());
651 const int length = collisionShapes.length();
652 for (int idx = 0; idx < length; idx++) {
653 const auto collisionShape = collisionShapes[idx];
654
655 if (!m_forceDebugDraw && !collisionShape->enableDebugDraw())
656 continue;
657
658 DebugModelHolder &holder =
659 m_collisionShapeDebugModels[std::make_pair(collisionShape, node)];
660 auto &model = holder.model;
661
662 currentCollisionShapes.insert(std::make_pair(collisionShape, node));
663
664 m_hasIndividualDebugDraw =
665 m_hasIndividualDebugDraw || collisionShape->enableDebugDraw();
666
667 // Create/Update debug view infrastructure
668 if (!model) {
669 model = new QQuick3DModel();
670 model->setParentItem(sceneNode);
671 model->setParent(sceneNode);
672 model->setCastsShadows(false);
673 model->setReceivesShadows(false);
674 model->setCastsReflections(false);
675 }
676
677 model->setVisible(true);
678
679 { // update or set material
680 auto material = m_debugMaterials[materialIdx];
681 QQmlListReference materialsRef(model, "materials");
682 if (materialsRef.count() == 0 || materialsRef.at(0) != material) {
683 materialsRef.clear();
684 materialsRef.append(material);
685 }
686 }
687
688 // Special handling of CharacterController since it has collision shapes,
689 // but not PhysX shapes
690 if (qobject_cast<QCharacterController *>(node->frontendNode)) {
691 QCapsuleShape *capsuleShape = qobject_cast<QCapsuleShape *>(collisionShape);
692 if (!capsuleShape)
693 continue;
694
695 const float radius = capsuleShape->diameter() * 0.5;
696 const float halfHeight = capsuleShape->height() * 0.5;
697
698 if (!qFuzzyCompare(radius, holder.radius())
699 || !qFuzzyCompare(halfHeight, holder.halfHeight())) {
700 auto geom = QDebugDrawHelper::generateCapsuleGeometry(radius, halfHeight);
701 geom->setParent(model);
702 model->setGeometry(geom);
703 holder.setRadius(radius);
704 holder.setHalfHeight(halfHeight);
705 }
706
707 model->setPosition(node->frontendNode->scenePosition());
708 model->setRotation(node->frontendNode->sceneRotation()
709 * QQuaternion::fromEulerAngles(0, 0, 90));
710 continue;
711 }
712
713 if (node->shapes.length() < length)
714 continue;
715
716 const auto physXShape = node->shapes[idx];
717 auto localPose = physXShape->getLocalPose();
718
719 switch (physXShape->getGeometryType()) {
720 case physx::PxGeometryType::eBOX: {
721 physx::PxBoxGeometry boxGeometry;
722 physXShape->getBoxGeometry(boxGeometry);
723 const auto &halfExtentsOld = holder.halfExtents();
724 const auto halfExtents = QPhysicsUtils::toQtType(boxGeometry.halfExtents);
725 if (!qFuzzyCompare(halfExtentsOld, halfExtents)) {
726 auto geom = QDebugDrawHelper::generateBoxGeometry(halfExtents);
727 geom->setParent(model);
728 model->setGeometry(geom);
729 holder.setHalfExtents(halfExtents);
730 }
731
732 }
733 break;
734
735 case physx::PxGeometryType::eSPHERE: {
736 physx::PxSphereGeometry sphereGeometry;
737 physXShape->getSphereGeometry(sphereGeometry);
738 const float radius = holder.radius();
739 if (!qFuzzyCompare(sphereGeometry.radius, radius)) {
740 auto geom = QDebugDrawHelper::generateSphereGeometry(sphereGeometry.radius);
741 geom->setParent(model);
742 model->setGeometry(geom);
743 holder.setRadius(sphereGeometry.radius);
744 }
745 }
746 break;
747
748 case physx::PxGeometryType::eCAPSULE: {
749 physx::PxCapsuleGeometry capsuleGeometry;
750 physXShape->getCapsuleGeometry(capsuleGeometry);
751 const float radius = holder.radius();
752 const float halfHeight = holder.halfHeight();
753
754 if (!qFuzzyCompare(capsuleGeometry.radius, radius)
755 || !qFuzzyCompare(capsuleGeometry.halfHeight, halfHeight)) {
756 auto geom = QDebugDrawHelper::generateCapsuleGeometry(
757 capsuleGeometry.radius, capsuleGeometry.halfHeight);
758 geom->setParent(model);
759 model->setGeometry(geom);
760 holder.setRadius(capsuleGeometry.radius);
761 holder.setHalfHeight(capsuleGeometry.halfHeight);
762 }
763 }
764 break;
765
766 case physx::PxGeometryType::ePLANE:{
767 physx::PxPlaneGeometry planeGeometry;
768 physXShape->getPlaneGeometry(planeGeometry);
769 // Special rotation
770 const QQuaternion rotation =
771 QPhysicsUtils::kMinus90YawRotation * QPhysicsUtils::toQtType(localPose.q);
772 localPose = physx::PxTransform(localPose.p, QPhysicsUtils::toPhysXType(rotation));
773
774 if (model->geometry() == nullptr) {
775 auto geom = QDebugDrawHelper::generatePlaneGeometry();
776 geom->setParent(model);
777 model->setGeometry(geom);
778 }
779 }
780 break;
781
782 // For heightfield, convex mesh and triangle mesh we increase its reference count
783 // to make sure it does not get dereferenced and deleted so that the new mesh will
784 // have another memory address so we know when it has changed.
785 case physx::PxGeometryType::eHEIGHTFIELD: {
786 physx::PxHeightFieldGeometry heightFieldGeometry;
787 bool success = physXShape->getHeightFieldGeometry(heightFieldGeometry);
788 Q_ASSERT(success);
789 const float heightScale = holder.heightScale();
790 const float rowScale = holder.rowScale();
791 const float columnScale = holder.columnScale();
792
793 if (auto heightField = holder.getHeightField();
794 heightField && heightField != heightFieldGeometry.heightField) {
795 heightField->release();
796 holder.setHeightField(nullptr);
797 }
798
799 if (!qFuzzyCompare(heightFieldGeometry.heightScale, heightScale)
800 || !qFuzzyCompare(heightFieldGeometry.rowScale, rowScale)
801 || !qFuzzyCompare(heightFieldGeometry.columnScale, columnScale)
802 || !holder.getHeightField()) {
803 if (!holder.getHeightField()) {
804 heightFieldGeometry.heightField->acquireReference();
805 holder.setHeightField(heightFieldGeometry.heightField);
806 }
807 auto geom = QDebugDrawHelper::generateHeightFieldGeometry(
808 heightFieldGeometry.heightField, heightFieldGeometry.heightScale,
809 heightFieldGeometry.rowScale, heightFieldGeometry.columnScale);
810 geom->setParent(model);
811 model->setGeometry(geom);
812 holder.setHeightScale(heightFieldGeometry.heightScale);
813 holder.setRowScale(heightFieldGeometry.rowScale);
814 holder.setColumnScale(heightFieldGeometry.columnScale);
815 }
816 }
817 break;
818
819 case physx::PxGeometryType::eCONVEXMESH: {
820 physx::PxConvexMeshGeometry convexMeshGeometry;
821 const bool success = physXShape->getConvexMeshGeometry(convexMeshGeometry);
822 Q_ASSERT(success);
823 const auto rotation = convexMeshGeometry.scale.rotation * localPose.q;
824 localPose = physx::PxTransform(localPose.p, rotation);
825 model->setScale(QPhysicsUtils::toQtType(convexMeshGeometry.scale.scale));
826
827 if (auto convexMesh = holder.getConvexMesh();
828 convexMesh && convexMesh != convexMeshGeometry.convexMesh) {
829 convexMesh->release();
830 holder.setConvexMesh(nullptr);
831 }
832
833 if (!model->geometry() || !holder.getConvexMesh()) {
834 if (!holder.getConvexMesh()) {
835 convexMeshGeometry.convexMesh->acquireReference();
836 holder.setConvexMesh(convexMeshGeometry.convexMesh);
837 }
838 auto geom = QDebugDrawHelper::generateConvexMeshGeometry(
839 convexMeshGeometry.convexMesh);
840 geom->setParent(model);
841 model->setGeometry(geom);
842 }
843 }
844 break;
845
846 case physx::PxGeometryType::eTRIANGLEMESH: {
847 physx::PxTriangleMeshGeometry triangleMeshGeometry;
848 const bool success = physXShape->getTriangleMeshGeometry(triangleMeshGeometry);
849 Q_ASSERT(success);
850 const auto rotation = triangleMeshGeometry.scale.rotation * localPose.q;
851 localPose = physx::PxTransform(localPose.p, rotation);
852 model->setScale(QPhysicsUtils::toQtType(triangleMeshGeometry.scale.scale));
853
854 if (auto triangleMesh = holder.getTriangleMesh();
855 triangleMesh && triangleMesh != triangleMeshGeometry.triangleMesh) {
856 triangleMesh->release();
857 holder.setTriangleMesh(nullptr);
858 }
859
860 if (!model->geometry() || !holder.getTriangleMesh()) {
861 if (!holder.getTriangleMesh()) {
862 triangleMeshGeometry.triangleMesh->acquireReference();
863 holder.setTriangleMesh(triangleMeshGeometry.triangleMesh);
864 }
865 auto geom = QDebugDrawHelper::generateTriangleMeshGeometry(
866 triangleMeshGeometry.triangleMesh);
867 geom->setParent(model);
868 model->setGeometry(geom);
869 }
870 }
871 break;
872
873 case physx::PxGeometryType::eINVALID:
874 case physx::PxGeometryType::eGEOMETRY_COUNT:
875 // should not happen
876 Q_UNREACHABLE();
877 }
878
879 auto globalPose = node->getGlobalPose();
880 auto finalPose = globalPose.transform(localPose);
881
882 model->setRotation(QPhysicsUtils::toQtType(finalPose.q));
883 model->setPosition(QPhysicsUtils::toQtType(finalPose.p));
884 }
885 }
886
887 // Remove old collision shapes
888 m_collisionShapeDebugModels.removeIf(
889 [&](QHash<QPair<QAbstractCollisionShape *, QAbstractPhysXNode *>,
890 DebugModelHolder>::iterator it) {
891 if (!currentCollisionShapes.contains(it.key())) {
892 auto holder = it.value();
893 holder.releaseMeshPointer();
894 if (holder.model)
895 delete holder.model;
896 return true;
897 }
898 return false;
899 });
900}
901
902static void collectPhysicsNodes(QQuick3DObject *node, QList<QAbstractPhysicsNode *> &nodes)
903{
904 if (auto shape = qobject_cast<QAbstractPhysicsNode *>(node)) {
905 nodes.push_back(shape);
906 return;
907 }
908
909 auto childItems = node->childItems();
910 for (QQuick3DObject *child : std::as_const(childItems))
911 collectPhysicsNodes(child, nodes);
912}
913
914void QPhysicsWorld::updateDebugDrawDesignStudio()
915{
916 // Use scene node if no viewport has been specified
917 auto sceneNode = m_viewport ? m_viewport : m_scene;
918
919 if (sceneNode == nullptr)
920 return;
921
922 setupDebugMaterials(sceneNode);
923
924 // Store the collision shapes we have now so we can clear out the removed ones
925 QSet<QPair<QAbstractCollisionShape *, QAbstractPhysicsNode *>> currentCollisionShapes;
926 currentCollisionShapes.reserve(m_collisionShapeDebugModels.size());
927
928 QList<QAbstractPhysicsNode *> activePhysicsNodes;
929 activePhysicsNodes.reserve(m_collisionShapeDebugModels.size());
930 collectPhysicsNodes(m_scene, activePhysicsNodes);
931
932 for (QAbstractPhysicsNode *node : std::as_const(activePhysicsNodes)) {
933
934 const auto &collisionShapes = node->getCollisionShapesList();
935 const int materialIdx = 0; // Just take first material
936 const int length = collisionShapes.length();
937
938 const bool isCharacterController = qobject_cast<QCharacterController *>(node) != nullptr;
939
940 for (int idx = 0; idx < length; idx++) {
941 QAbstractCollisionShape *collisionShape = collisionShapes[idx];
942 DebugModelHolder &holder =
943 m_DesignStudioDebugModels[std::make_pair(collisionShape, node)];
944 auto &model = holder.model;
945
946 currentCollisionShapes.insert(std::make_pair(collisionShape, node));
947
948 m_hasIndividualDebugDraw =
949 m_hasIndividualDebugDraw || collisionShape->enableDebugDraw();
950
951 // Create/Update debug view infrastructure
952 {
953 // Hack: we have to delete the model every frame so it shows up in QDS
954 // whenever the code is updated, not sure why ¯\_(?)_/¯
955 delete model;
956 model = new QQuick3DModel();
957 model->setParentItem(sceneNode);
958 model->setParent(sceneNode);
959 model->setCastsShadows(false);
960 model->setReceivesShadows(false);
961 model->setCastsReflections(false);
962 }
963
964 const bool hasGeometry = holder.geometry != nullptr;
965 QVector3D scenePosition = collisionShape->scenePosition();
966 QQuaternion sceneRotation = collisionShape->sceneRotation();
967 QQuick3DGeometry *newGeometry = nullptr;
968
969 if (isCharacterController)
970 sceneRotation = sceneRotation * QQuaternion::fromEulerAngles(QVector3D(0, 0, 90));
971
972 { // update or set material
973 auto material = m_debugMaterials[materialIdx];
974 QQmlListReference materialsRef(model, "materials");
975 if (materialsRef.count() == 0 || materialsRef.at(0) != material) {
976 materialsRef.clear();
977 materialsRef.append(material);
978 }
979 }
980
981 if (auto shape = qobject_cast<QBoxShape *>(collisionShape)) {
982 const auto &halfExtentsOld = holder.halfExtents();
983 const auto halfExtents = shape->sceneScale() * shape->extents() * 0.5f;
984 if (!qFuzzyCompare(halfExtentsOld, halfExtents) || !hasGeometry) {
985 newGeometry = QDebugDrawHelper::generateBoxGeometry(halfExtents);
986 holder.setHalfExtents(halfExtents);
987 }
988 } else if (auto shape = qobject_cast<QSphereShape *>(collisionShape)) {
989 const float radiusOld = holder.radius();
990 const float radius = shape->sceneScale().x() * shape->diameter() * 0.5f;
991 if (!qFuzzyCompare(radiusOld, radius) || !hasGeometry) {
992 newGeometry = QDebugDrawHelper::generateSphereGeometry(radius);
993 holder.setRadius(radius);
994 }
995 } else if (auto shape = qobject_cast<QCapsuleShape *>(collisionShape)) {
996 const float radiusOld = holder.radius();
997 const float halfHeightOld = holder.halfHeight();
998 const float radius = shape->sceneScale().y() * shape->diameter() * 0.5f;
999 const float halfHeight = shape->sceneScale().x() * shape->height() * 0.5f;
1000
1001 if ((!qFuzzyCompare(radiusOld, radius) || !qFuzzyCompare(halfHeightOld, halfHeight))
1002 || !hasGeometry) {
1003 newGeometry = QDebugDrawHelper::generateCapsuleGeometry(radius, halfHeight);
1004 holder.setRadius(radius);
1005 holder.setHalfHeight(halfHeight);
1006 }
1007 } else if (qobject_cast<QPlaneShape *>(collisionShape)) {
1008 if (!hasGeometry)
1009 newGeometry = QDebugDrawHelper::generatePlaneGeometry();
1010 } else if (auto shape = qobject_cast<QHeightFieldShape *>(collisionShape)) {
1011 physx::PxHeightFieldGeometry *heightFieldGeometry =
1012 static_cast<physx::PxHeightFieldGeometry *>(shape->getPhysXGeometry());
1013 const float heightScale = holder.heightScale();
1014 const float rowScale = holder.rowScale();
1015 const float columnScale = holder.columnScale();
1016 scenePosition += shape->hfOffset();
1017 if (!heightFieldGeometry) {
1018 qWarning() << "Could not get height field";
1019 } else if (!qFuzzyCompare(heightFieldGeometry->heightScale, heightScale)
1020 || !qFuzzyCompare(heightFieldGeometry->rowScale, rowScale)
1021 || !qFuzzyCompare(heightFieldGeometry->columnScale, columnScale)
1022 || !hasGeometry) {
1023 newGeometry = QDebugDrawHelper::generateHeightFieldGeometry(
1024 heightFieldGeometry->heightField, heightFieldGeometry->heightScale,
1025 heightFieldGeometry->rowScale, heightFieldGeometry->columnScale);
1026 holder.setHeightScale(heightFieldGeometry->heightScale);
1027 holder.setRowScale(heightFieldGeometry->rowScale);
1028 holder.setColumnScale(heightFieldGeometry->columnScale);
1029 }
1030 } else if (auto shape = qobject_cast<QConvexMeshShape *>(collisionShape)) {
1031 auto convexMeshGeometry =
1032 static_cast<physx::PxConvexMeshGeometry *>(shape->getPhysXGeometry());
1033 if (!convexMeshGeometry) {
1034 qWarning() << "Could not get convex mesh";
1035 } else {
1036 model->setScale(QPhysicsUtils::toQtType(convexMeshGeometry->scale.scale));
1037
1038 if (!hasGeometry) {
1039 newGeometry = QDebugDrawHelper::generateConvexMeshGeometry(
1040 convexMeshGeometry->convexMesh);
1041 }
1042 }
1043 } else if (auto shape = qobject_cast<QTriangleMeshShape *>(collisionShape)) {
1044 physx::PxTriangleMeshGeometry *triangleMeshGeometry =
1045 static_cast<physx::PxTriangleMeshGeometry *>(shape->getPhysXGeometry());
1046 if (!triangleMeshGeometry) {
1047 qWarning() << "Could not get triangle mesh";
1048 } else {
1049 model->setScale(QPhysicsUtils::toQtType(triangleMeshGeometry->scale.scale));
1050
1051 if (!hasGeometry) {
1052 newGeometry = QDebugDrawHelper::generateTriangleMeshGeometry(
1053 triangleMeshGeometry->triangleMesh);
1054 }
1055 }
1056 }
1057
1058 if (newGeometry) {
1059 delete holder.geometry;
1060 holder.geometry = newGeometry;
1061 }
1062
1063 model->setGeometry(holder.geometry);
1064 model->setVisible(true);
1065
1066 model->setRotation(sceneRotation);
1067 model->setPosition(scenePosition);
1068 }
1069 }
1070
1071 // Remove old debug models
1072 m_DesignStudioDebugModels.removeIf(
1073 [&](QHash<QPair<QAbstractCollisionShape *, QAbstractPhysicsNode *>,
1074 DebugModelHolder>::iterator it) {
1075 if (!currentCollisionShapes.contains(it.key())) {
1076 auto holder = it.value();
1077 holder.releaseMeshPointer();
1078 if (holder.model) {
1079 delete holder.geometry;
1080 delete holder.model;
1081 }
1082 return true;
1083 }
1084 return false;
1085 });
1086}
1087
1088void QPhysicsWorld::disableDebugDraw()
1089{
1090 m_hasIndividualDebugDraw = false;
1091
1092 for (QAbstractPhysXNode *body : std::as_const(m_physXBodies)) {
1093 const auto &collisionShapes = body->frontendNode->getCollisionShapesList();
1094 const int length = collisionShapes.length();
1095 for (int idx = 0; idx < length; idx++) {
1096 const auto collisionShape = collisionShapes[idx];
1097 if (collisionShape->enableDebugDraw()) {
1098 m_hasIndividualDebugDraw = true;
1099 return;
1100 }
1101 }
1102 }
1103}
1104
1105void QPhysicsWorld::setEnableCCD(bool enableCCD)
1106{
1107 if (m_enableCCD == enableCCD)
1108 return;
1109
1110 if (m_physicsInitialized) {
1111 qWarning()
1112 << "Warning: Changing 'enableCCD' after physics is initialized will have no effect";
1113 return;
1114 }
1115
1116 m_enableCCD = enableCCD;
1117 emit enableCCDChanged(m_enableCCD);
1118}
1119
1120void QPhysicsWorld::setTypicalLength(float typicalLength)
1121{
1122 if (qFuzzyCompare(typicalLength, m_typicalLength))
1123 return;
1124
1125 if (typicalLength <= 0.f) {
1126 qWarning() << "Warning: 'typicalLength' value less than zero, ignored";
1127 return;
1128 }
1129
1130 if (m_physicsInitialized) {
1131 qWarning() << "Warning: Changing 'typicalLength' after physics is initialized will have "
1132 "no effect";
1133 return;
1134 }
1135
1136 m_typicalLength = typicalLength;
1137
1138 emit typicalLengthChanged(typicalLength);
1139}
1140
1141void QPhysicsWorld::setTypicalSpeed(float typicalSpeed)
1142{
1143 if (qFuzzyCompare(typicalSpeed, m_typicalSpeed))
1144 return;
1145
1146 if (m_physicsInitialized) {
1147 qWarning() << "Warning: Changing 'typicalSpeed' after physics is initialized will have "
1148 "no effect";
1149 return;
1150 }
1151
1152 m_typicalSpeed = typicalSpeed;
1153
1154 emit typicalSpeedChanged(typicalSpeed);
1155}
1156
1157float QPhysicsWorld::defaultDensity() const
1158{
1159 return m_defaultDensity;
1160}
1161
1162float QPhysicsWorld::minimumTimestep() const
1163{
1164 return m_minTimestep;
1165}
1166
1167float QPhysicsWorld::maximumTimestep() const
1168{
1169 return m_maxTimestep;
1170}
1171
1172void QPhysicsWorld::setDefaultDensity(float defaultDensity)
1173{
1174 if (qFuzzyCompare(m_defaultDensity, defaultDensity))
1175 return;
1176 m_defaultDensity = defaultDensity;
1177
1178 // Go through all dynamic rigid bodies and update the default density
1179 for (QAbstractPhysXNode *body : std::as_const(m_physXBodies))
1180 body->updateDefaultDensity(m_defaultDensity);
1181
1182 emit defaultDensityChanged(defaultDensity);
1183}
1184
1185// Remove physics world items that no longer exist
1186
1187void QPhysicsWorld::cleanupRemovedNodes()
1188{
1189 m_physXBodies.removeIf([this](QAbstractPhysXNode *body) {
1190 return body->cleanupIfRemoved(m_physx);
1191 });
1192 m_removedPhysicsNodes.clear();
1193}
1194
1195void QPhysicsWorld::initPhysics()
1196{
1197 Q_ASSERT(!m_physicsInitialized);
1198
1199 const unsigned int numThreads = m_numThreads >= 0 ? m_numThreads : qMax(0, QThread::idealThreadCount());
1200 m_physx->createScene(m_typicalLength, m_typicalSpeed, m_gravity, m_enableCCD, this, numThreads);
1201 m_frameAnimator->start();
1202 m_physicsInitialized = true;
1203}
1204
1205void QPhysicsWorld::simulateFrame()
1206{
1207 constexpr double MILLIONTH = 0.000001;
1208 constexpr double THOUSANDTH = 0.001;
1209
1210 if (m_inDesignStudio) {
1211 frameFinishedDesignStudio();
1212 return;
1213 }
1214
1215 if (!m_physx->isRunning) {
1216 m_timer.start();
1217 m_physx->isRunning = true;
1218 const double minTimestepSecs = m_minTimestep * 0.001;
1219 m_physx->scene->simulate(minTimestepSecs);
1220 m_currTimeStep = minTimestepSecs;
1221 return;
1222 }
1223
1224 // Frame not ready yet
1225 if (!m_frameFetched && !m_physx->scene->checkResults()) {
1226 return;
1227 }
1228
1229 // Frame ready, fetch and finish it
1230 if (!m_frameFetched) {
1231 m_physx->scene->fetchResults(true);
1232 frameFinished(m_currTimeStep);
1233 m_frameFetched = true;
1234 if (Q_UNLIKELY(!qtPhysicsTimingsFile.isEmpty())) {
1235 const double deltaMS = m_timer.nsecsElapsed() * MILLIONTH;
1236 m_frameTimings.append(deltaMS);
1237 }
1238 }
1239
1240 // Assuming: 0 <= minTimestep <= maxTimestep
1241 const double deltaMS = m_timer.nsecsElapsed() * MILLIONTH;
1242 if (deltaMS < m_minTimestep)
1243 return;
1244 const double deltaSecs = qMin<double>(deltaMS, m_maxTimestep) * THOUSANDTH;
1245 m_timer.restart();
1246 m_physx->scene->simulate(deltaSecs);
1247 m_frameFetched = false;
1248 m_currTimeStep = deltaSecs;
1249}
1250
1251void QPhysicsWorld::frameFinished(float deltaTime)
1252{
1253 matchOrphanNodes();
1254 emitContactCallbacks();
1255 cleanupRemovedNodes();
1256 for (auto *node : std::as_const(m_newPhysicsNodes)) {
1257 auto *body = node->createPhysXBackend();
1258 body->init(this, m_physx);
1259 m_physXBodies.push_back(body);
1260 }
1261 m_newPhysicsNodes.clear();
1262
1263 QHash<QQuick3DNode *, QMatrix4x4> transformCache;
1264
1265 // TODO: Use dirty flag/dirty list to avoid redoing things that didn't change
1266 for (auto *physXBody : std::as_const(m_physXBodies)) {
1267 physXBody->markDirtyShapes();
1268 physXBody->rebuildDirtyShapes(this, m_physx);
1269 physXBody->updateFilters();
1270
1271 // Sync the physics world and the scene
1272 physXBody->sync(deltaTime, transformCache);
1273 }
1274
1275 updateDebugDraw();
1276 emit frameDone(deltaTime * 1000);
1277}
1278
1279void QPhysicsWorld::frameFinishedDesignStudio()
1280{
1281 // Note sure if this is needed but do it anyway
1282 matchOrphanNodes();
1283 emitContactCallbacks();
1284 cleanupRemovedNodes();
1285 // Ignore new physics nodes, we find them from the scene node anyway
1286 m_newPhysicsNodes.clear();
1287
1288 updateDebugDrawDesignStudio();
1289}
1290
1291QPhysicsWorld *QPhysicsWorld::getWorld(QQuick3DNode *node)
1292{
1293 for (QPhysicsWorld *world : std::as_const(worldManager.worlds)) {
1294 if (!world->m_scene) {
1295 continue;
1296 }
1297
1298 QQuick3DNode *nodeCurr = node;
1299
1300 // Maybe pointless but check starting node
1301 if (nodeCurr == world->m_scene)
1302 return world;
1303
1304 while (nodeCurr->parentNode()) {
1305 nodeCurr = nodeCurr->parentNode();
1306 if (nodeCurr == world->m_scene)
1307 return world;
1308 }
1309 }
1310
1311 return nullptr;
1312}
1313
1314void QPhysicsWorld::matchOrphanNodes()
1315{
1316 // FIXME: does this need thread safety?
1317 if (worldManager.orphanNodes.isEmpty())
1318 return;
1319
1320 qsizetype numNodes = worldManager.orphanNodes.length();
1321 qsizetype idx = 0;
1322
1323 while (idx < numNodes) {
1324 auto node = worldManager.orphanNodes[idx];
1325 auto world = getWorld(node);
1326 if (world == this) {
1327 world->m_newPhysicsNodes.push_back(node);
1328 // swap-erase
1329 worldManager.orphanNodes.swapItemsAt(idx, numNodes - 1);
1330 worldManager.orphanNodes.pop_back();
1331 numNodes--;
1332 } else {
1333 idx++;
1334 }
1335 }
1336}
1337
1338void QPhysicsWorld::findPhysicsNodes()
1339{
1340 // This method finds the physics nodes inside the scene pointed to by the
1341 // scene property. This method is necessary to run whenever the scene
1342 // property is changed.
1343 if (m_scene == nullptr)
1344 return;
1345
1346 // Recursively go through all children and add all QAbstractPhysicsNode's
1347 QList<QQuick3DObject *> children = m_scene->childItems();
1348 while (!children.empty()) {
1349 auto child = children.takeFirst();
1350 if (auto converted = qobject_cast<QAbstractPhysicsNode *>(child); converted != nullptr) {
1351 // This should never happen but check anyway.
1352 if (converted->m_backendObject != nullptr) {
1353 qWarning() << "Warning: physics node already associated with a backend node.";
1354 continue;
1355 }
1356
1357 m_newPhysicsNodes.push_back(converted);
1358 worldManager.orphanNodes.removeAll(converted); // No longer orphan
1359 }
1360 children.append(child->childItems());
1361 }
1362}
1363
1364void QPhysicsWorld::emitContactCallbacks()
1365{
1366 for (const QPhysicsWorld::BodyContact &contact : std::as_const(m_registeredContacts)) {
1367 if (m_removedPhysicsNodes.contains(contact.sender)
1368 || m_removedPhysicsNodes.contains(contact.receiver))
1369 continue;
1370 contact.receiver->registerContact(contact.sender, contact.positions, contact.impulses,
1371 contact.normals);
1372 }
1373
1374 m_registeredContacts.clear();
1375}
1376
1377physx::PxPhysics *QPhysicsWorld::getPhysics()
1378{
1379 return StaticPhysXObjects::getReference().physics;
1380}
1381
1382physx::PxCooking *QPhysicsWorld::getCooking()
1383{
1384 return StaticPhysXObjects::getReference().cooking;
1385}
1386
1387physx::PxControllerManager *QPhysicsWorld::controllerManager()
1388{
1389 if (m_physx->scene && !m_physx->controllerManager) {
1390 m_physx->controllerManager = PxCreateControllerManager(*m_physx->scene);
1391 qCDebug(lcQuick3dPhysics) << "Created controller manager" << m_physx->controllerManager;
1392 }
1393 return m_physx->controllerManager;
1394}
1395
1396QQuick3DNode *QPhysicsWorld::scene() const
1397{
1398 return m_scene;
1399}
1400
1401void QPhysicsWorld::setScene(QQuick3DNode *newScene)
1402{
1403 if (m_scene == newScene)
1404 return;
1405
1406 m_scene = newScene;
1407
1408 // Delete all nodes since they are associated with the previous scene
1409 for (auto body : std::as_const(m_physXBodies)) {
1410 deregisterNode(body->frontendNode);
1411 }
1412
1413 // Check if scene is already used by another world
1414 bool sceneOK = true;
1415 for (QPhysicsWorld *world : std::as_const(worldManager.worlds)) {
1416 if (world != this && world->scene() == newScene) {
1417 sceneOK = false;
1418 qWarning() << "Warning: scene already associated with physics world";
1419 }
1420 }
1421
1422 if (sceneOK)
1423 findPhysicsNodes();
1424 emit sceneChanged();
1425}
1426
1427int QPhysicsWorld::numThreads() const
1428{
1429 return m_numThreads;
1430}
1431
1432void QPhysicsWorld::setNumThreads(int newNumThreads)
1433{
1434 if (m_numThreads == newNumThreads)
1435 return;
1436 m_numThreads = newNumThreads;
1437 emit numThreadsChanged();
1438}
1439
1440bool QPhysicsWorld::reportKinematicKinematicCollisions() const
1441{
1442 return m_reportKinematicKinematicCollisions;
1443}
1444
1445void QPhysicsWorld::setReportKinematicKinematicCollisions(
1446 bool newReportKinematicKinematicCollisions)
1447{
1448 if (m_reportKinematicKinematicCollisions == newReportKinematicKinematicCollisions)
1449 return;
1450 m_reportKinematicKinematicCollisions = newReportKinematicKinematicCollisions;
1451 emit reportKinematicKinematicCollisionsChanged();
1452}
1453
1454bool QPhysicsWorld::reportStaticKinematicCollisions() const
1455{
1456 return m_reportStaticKinematicCollisions;
1457}
1458
1459void QPhysicsWorld::setReportStaticKinematicCollisions(bool newReportStaticKinematicCollisions)
1460{
1461 if (m_reportStaticKinematicCollisions == newReportStaticKinematicCollisions)
1462 return;
1463 m_reportStaticKinematicCollisions = newReportStaticKinematicCollisions;
1464 emit reportStaticKinematicCollisionsChanged();
1465}
1466
1468
1469#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