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