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