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

source code of qtquick3dphysics/src/quick3dphysics/qphysicsworld.cpp