Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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
4#include "qphysicsworld_p.h"
5
10#include "qphysicsutils_p.h"
12#include "qboxshape_p.h"
13#include "qsphereshape_p.h"
14#include "qconvexmeshshape_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
35
213Q_LOGGING_CATEGORY(lcQuick3dPhysics, "qt.quick3d.physics");
214
216
218{
220public:
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(float(deltaMS), maxTimestep) * 0.001f;
245 m_physx->scene->simulate(deltaSecs);
246 m_physx->scene->fetchResults(true);
247
248 emit frameDone(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);
258 }
259
260signals:
261 void frameDone(float deltaTime);
263
264private:
265 QPhysXWorld *m_physx = nullptr;
266 QElapsedTimer m_timer;
267};
268
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
352
354{
355 QVector<QPhysicsWorld *> worlds;
356 QVector<QAbstractPhysicsNode *> orphanNodes;
357};
358
360
362{
363 auto world = getWorld(physicsNode);
364 if (world) {
365 world->m_newPhysicsNodes.push_back(physicsNode);
366 } else {
367 worldManager.orphanNodes.push_back(physicsNode);
368 }
369}
370
372{
373 for (auto world : worldManager.worlds) {
374 world->m_newPhysicsNodes.removeAll(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(physicsNode);
383 }
384 worldManager.orphanNodes.removeAll(physicsNode);
385}
386
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(contact);
406}
407
409{
410 m_inDesignStudio = !qEnvironmentVariableIsEmpty("QML_PUPPET_MODE");
411 m_physx = new QPhysXWorld;
412 m_physx->createWorld();
413
414 worldManager.worlds.push_back(this);
415 matchOrphanNodes();
416}
417
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(this);
429}
430
432
434{
435 if ((!m_running && !m_inDesignStudio) || m_physicsInitialized)
436 return;
437 initPhysics();
438 emit simulateFrame(m_minTimestep, m_maxTimestep);
439}
440
442{
443 return m_gravity;
444}
445
447{
448 return m_running;
449}
450
452{
453 return m_forceDebugDraw;
454}
455
457{
458 return m_enableCCD;
459}
460
462{
463 return m_typicalLength;
464}
465
467{
468 return m_typicalSpeed;
469}
470
472{
473 return m_removedPhysicsNodes.contains(object);
474}
475
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(m_gravity));
484 }
485 emit gravityChanged(m_gravity);
486}
487
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(m_minTimestep, m_maxTimestep);
499 }
500 emit runningChanged(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(m_forceDebugDraw);
514}
515
517{
518 return m_viewport;
519}
520
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(m_viewport);
545}
546
547void QPhysicsWorld::setMinimumTimestep(float minTimestep)
548{
549 if (qFuzzyCompare(m_minTimestep, minTimestep))
550 return;
551
552 if (minTimestep > m_maxTimestep) {
553 qWarning("Minimum timestep greater than maximum timestep, value clamped");
554 minTimestep = qMin(minTimestep, m_maxTimestep);
555 }
556
557 if (minTimestep < 0.f) {
558 qWarning("Minimum timestep less than zero, value clamped");
559 minTimestep = qMax(minTimestep, 0.f);
560 }
561
562 if (qFuzzyCompare(m_minTimestep, minTimestep))
563 return;
564
565 m_minTimestep = minTimestep;
566 emit minimumTimestepChanged(m_minTimestep);
567}
568
569void QPhysicsWorld::setMaximumTimestep(float maxTimestep)
570{
571 if (qFuzzyCompare(m_maxTimestep, maxTimestep))
572 return;
573
574 if (maxTimestep < 0.f) {
575 qWarning("Maximum timestep less than zero, value clamped");
576 maxTimestep = qMax(maxTimestep, 0.f);
577 }
578
579 if (qFuzzyCompare(m_maxTimestep, 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
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(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(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(collisionShape, node)];
648 auto &model = holder.model;
649
650 currentCollisionShapes.insert(std::make_pair(collisionShape, 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 *>(node->frontendNode)) {
679 QCapsuleShape *capsuleShape = qobject_cast<QCapsuleShape *>(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(radius, holder.radius())
687 || !qFuzzyCompare(halfHeight, 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(0, 0, 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(boxGeometry);
711 const auto &halfExtentsOld = holder.halfExtents();
712 const auto halfExtents = QPhysicsUtils::toQtType(boxGeometry.halfExtents);
713 if (!qFuzzyCompare(halfExtentsOld, 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(sphereGeometry);
726 const float radius = holder.radius();
727 if (!qFuzzyCompare(sphereGeometry.radius, radius)) {
728 auto geom = QDebugDrawHelper::generateSphereGeometry(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(capsuleGeometry);
739 const float radius = holder.radius();
740 const float halfHeight = holder.halfHeight();
741
742 if (!qFuzzyCompare(capsuleGeometry.radius, radius)
743 || !qFuzzyCompare(capsuleGeometry.halfHeight, halfHeight)) {
745 capsuleGeometry.radius, 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(planeGeometry);
757 // Special rotation
758 const QQuaternion rotation =
760 localPose = physx::PxTransform(localPose.p, QPhysicsUtils::toPhysXType(rotation));
761
762 if (model->geometry() == nullptr) {
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(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(heightFieldGeometry.heightScale, heightScale)
788 || !qFuzzyCompare(heightFieldGeometry.rowScale, rowScale)
789 || !qFuzzyCompare(heightFieldGeometry.columnScale, columnScale)
790 || !holder.getHeightField()) {
791 if (!holder.getHeightField()) {
792 heightFieldGeometry.heightField->acquireReference();
793 holder.setHeightField(heightFieldGeometry.heightField);
794 }
796 heightFieldGeometry.heightField, heightFieldGeometry.heightScale,
797 heightFieldGeometry.rowScale, 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(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(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 }
827 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(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(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 }
854 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(localPose);
869
870 model->setRotation(QPhysicsUtils::toQtType(finalPose.q));
871 model->setPosition(QPhysicsUtils::toQtType(finalPose.p));
872 }
873 }
874
875 // Remove old collision shapes
876 m_collisionShapeDebugModels.removeIf(
877 [&](QHash<QPair<QAbstractCollisionShape *, QAbstractPhysXNode *>,
878 DebugModelHolder>::iterator it) {
879 if (!currentCollisionShapes.contains(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 *>(node)) {
893 nodes.push_back(shape);
894 return;
895 }
896
897 for (QQuick3DObject *child : node->childItems())
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(m_collisionShapeDebugModels.size());
914
915 QList<QAbstractPhysicsNode *> activePhysicsNodes;
916 activePhysicsNodes.reserve(m_collisionShapeDebugModels.size());
917 collectPhysicsNodes(m_scene, 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 *>(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(collisionShape, node)];
931 auto &model = holder.model;
932
933 currentCollisionShapes.insert(std::make_pair(collisionShape, 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(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 *>(collisionShape)) {
969 const auto &halfExtentsOld = holder.halfExtents();
970 const auto halfExtents = shape->sceneScale() * shape->extents() * 0.5f;
971 if (!qFuzzyCompare(halfExtentsOld, halfExtents) || !hasGeometry) {
972 newGeometry = QDebugDrawHelper::generateBoxGeometry(halfExtents);
973 holder.setHalfExtents(halfExtents);
974 }
975 } else if (auto shape = qobject_cast<QSphereShape *>(collisionShape)) {
976 const float radiusOld = holder.radius();
977 const float radius = shape->sceneScale().x() * shape->diameter() * 0.5f;
978 if (!qFuzzyCompare(radiusOld, radius) || !hasGeometry) {
979 newGeometry = QDebugDrawHelper::generateSphereGeometry(radius);
980 holder.setRadius(radius);
981 }
982 } else if (auto shape = qobject_cast<QCapsuleShape *>(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(radiusOld, radius) || !qFuzzyCompare(halfHeightOld, halfHeight))
989 || !hasGeometry) {
990 newGeometry = QDebugDrawHelper::generateCapsuleGeometry(radius, halfHeight);
991 holder.setRadius(radius);
992 holder.setHalfHeight(halfHeight);
993 }
994 } else if (qobject_cast<QPlaneShape *>(collisionShape)) {
995 if (!hasGeometry)
997 } else if (auto shape = qobject_cast<QHeightFieldShape *>(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(heightFieldGeometry->heightScale, heightScale)
1007 || !qFuzzyCompare(heightFieldGeometry->rowScale, rowScale)
1008 || !qFuzzyCompare(heightFieldGeometry->columnScale, columnScale)
1009 || !hasGeometry) {
1011 heightFieldGeometry->heightField, heightFieldGeometry->heightScale,
1012 heightFieldGeometry->rowScale, 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 *>(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(convexMeshGeometry->scale.scale));
1024
1025 if (!hasGeometry) {
1027 convexMeshGeometry->convexMesh);
1028 }
1029 }
1030 } else if (auto shape = qobject_cast<QTriangleMeshShape *>(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(triangleMeshGeometry->scale.scale));
1037
1038 if (!hasGeometry) {
1040 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 [&](QHash<QPair<QAbstractCollisionShape *, QAbstractPhysicsNode *>,
1061 DebugModelHolder>::iterator it) {
1062 if (!currentCollisionShapes.contains(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
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(m_enableCCD);
1105}
1106
1107void QPhysicsWorld::setTypicalLength(float typicalLength)
1108{
1109 if (qFuzzyCompare(typicalLength, 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
1126}
1127
1128void QPhysicsWorld::setTypicalSpeed(float typicalSpeed)
1129{
1130 if (qFuzzyCompare(typicalSpeed, 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
1142}
1143
1145{
1146 return m_defaultDensity;
1147}
1148
1150{
1151 return m_minTimestep;
1152}
1153
1155{
1156 return m_maxTimestep;
1157}
1158
1159void QPhysicsWorld::setDefaultDensity(float defaultDensity)
1160{
1161 if (qFuzzyCompare(m_defaultDensity, 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(m_defaultDensity);
1168
1170}
1171
1172// Remove physics world items that no longer exist
1173
1174void QPhysicsWorld::cleanupRemovedNodes()
1175{
1176 m_physXBodies.removeIf([this](QAbstractPhysXNode *body) {
1177 return body->cleanupIfRemoved(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(0, QThread::idealThreadCount());
1189 m_physx->createScene(m_typicalLength, m_typicalSpeed, m_gravity, m_enableCCD, this, numThreads);
1190
1191 // Setup worker thread
1192 SimulationWorker *worker = new SimulationWorker(m_physx);
1193 worker->moveToThread(&m_workerThread);
1194 connect(&m_workerThread, &QThread::finished, worker, &QObject::deleteLater);
1195 if (m_inDesignStudio) {
1199 &QPhysicsWorld::frameFinishedDesignStudio);
1200 } else {
1202 connect(worker, &SimulationWorker::frameDone, this, &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(m_newPhysicsNodes)) {
1215 auto *body = node->createPhysXBackend();
1216 body->init(this, m_physx);
1217 m_physXBodies.push_back(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(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(m_minTimestep, m_maxTimestep);
1237 emit frameDone(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(m_minTimestep, m_maxTimestep);
1252}
1253
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(node);
1291 // swap-erase
1292 worldManager.orphanNodes.swapItemsAt(idx, 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 *>(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(converted);
1321 worldManager.orphanNodes.removeAll(converted); // No longer orphan
1322 }
1323 children.append(child->childItems());
1324 }
1325}
1326
1327void QPhysicsWorld::emitContactCallbacks()
1328{
1329 for (const QPhysicsWorld::BodyContact &contact : m_registeredContacts) {
1330 if (m_removedPhysicsNodes.contains(contact.sender)
1331 || m_removedPhysicsNodes.contains(contact.receiver))
1332 continue;
1333 contact.receiver->registerContact(contact.sender, contact.positions, contact.impulses,
1334 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(*m_physx->scene);
1354 qCDebug(lcQuick3dPhysics) << "Created controller manager" << m_physx->controllerManager;
1355 }
1356 return m_physx->controllerManager;
1357}
1358
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) {
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
1404{
1405 return m_reportKinematicKinematicCollisions;
1406}
1407
1409 bool newReportKinematicKinematicCollisions)
1410{
1411 if (m_reportKinematicKinematicCollisions == newReportKinematicKinematicCollisions)
1412 return;
1413 m_reportKinematicKinematicCollisions = newReportKinematicKinematicCollisions;
1414 emit reportKinematicKinematicCollisionsChanged();
1415}
1416
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
1431
1432#include "qphysicsworld.moc"
QAbstractPhysicsNode * frontendNode
virtual void init(QPhysicsWorld *world, QPhysXWorld *physX)=0
bool cleanupIfRemoved(QPhysXWorld *physX)
\inmodule QtCore
qint64 restart() noexcept
Restarts the timer and returns the number of milliseconds elapsed since the previous start.
void start() noexcept
\typealias QElapsedTimer::Duration Synonym for std::chrono::nanoseconds.
qint64 nsecsElapsed() const noexcept
\inmodule QtCore
Definition qhash.h:820
qsizetype size() const noexcept
Returns the number of items in the hash.
Definition qhash.h:927
qsizetype removeIf(Predicate pred)
Definition qhash.h:980
void clear() noexcept(std::is_nothrow_destructible< Node >::value)
Removes all items from the hash and frees up all memory used by it.
Definition qhash.h:951
bool empty() const noexcept
Definition qlist.h:685
void push_back(parameter_type t)
Definition qlist.h:675
value_type takeFirst()
Definition qlist.h:566
qsizetype removeIf(Predicate pred)
Definition qlist.h:604
void append(parameter_type t)
Definition qlist.h:458
void clear()
Definition qlist.h:434
\inmodule QtCore
Definition qmutex.h:313
\inmodule QtCore
Definition qobject.h:103
const QObjectList & children() const
Returns a list of child objects.
Definition qobject.h:201
QObject * parent() const
Returns a pointer to the parent object.
Definition qobject.h:346
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
QObject * sender() const
Returns a pointer to the object that sent the signal, if called in a slot activated by a signal; othe...
Definition qobject.cpp:2658
void setParent(QObject *parent)
Makes the object a child of parent.
Definition qobject.cpp:2195
bool moveToThread(QThread *thread QT6_DECL_NEW_OVERLOAD_TAIL)
Changes the thread affinity for this object and its children and returns true on success.
Definition qobject.cpp:1643
void deleteLater()
\threadsafe
Definition qobject.cpp:2435
void createWorld()
void deleteWorld()
physx::PxScene * scene
void createScene(float typicalLength, float typicalSpeed, const QVector3D &gravity, bool enableCCD, QPhysicsWorld *physicsWorld, unsigned int numThreads)
physx::PxControllerManager * controllerManager
QVector3D gravity
void typicalSpeedChanged(float typicalSpeed)
void componentComplete() override
Invoked after the root component that caused this instantiation has completed construction.
QQuick3DNode * scene
void classBegin() override
Invoked after class creation, but before any properties have been set.
void setRunning(bool running)
QPhysicsWorld(QObject *parent=nullptr)
bool isNodeRemoved(QAbstractPhysicsNode *object)
void registerContact(QAbstractPhysicsNode *sender, QAbstractPhysicsNode *receiver, const QVector< QVector3D > &positions, const QVector< QVector3D > &impulses, const QVector< QVector3D > &normals)
void setDefaultDensity(float defaultDensity)
void forceDebugDrawChanged(bool forceDebugDraw)
bool reportKinematicKinematicCollisions
static QPhysicsWorld * getWorld(QQuick3DNode *node)
void setHasIndividualDebugDraw()
void enableCCDChanged(bool enableCCD)
void setTypicalLength(float typicalLength)
void runningChanged(bool running)
void setTypicalSpeed(float typicalSpeed)
void setForceDebugDraw(bool forceDebugDraw)
QQuick3DNode * viewport
void typicalLengthChanged(float typicalLength)
void setEnableCCD(bool enableCCD)
physx::PxControllerManager * controllerManager()
void setReportStaticKinematicCollisions(bool newReportStaticKinematicCollisions)
void simulateFrame(float minTimestep, float maxTimestep)
void defaultDensityChanged(float defaultDensity)
void setReportKinematicKinematicCollisions(bool newReportKinematicKinematicCollisions)
void setGravity(QVector3D gravity)
static void registerNode(QAbstractPhysicsNode *physicsNode)
bool reportStaticKinematicCollisions
static void deregisterNode(QAbstractPhysicsNode *physicsNode)
void gravityChanged(QVector3D gravity)
The QQmlListReference class allows the manipulation of QQmlListProperty properties.
Definition qqmllist.h:183
The QQuaternion class represents a quaternion consisting of a vector and scalar.
\qmltype Geometry \inherits Object3D \inqmlmodule QtQuick3D \instantiates QQuick3DGeometry
\qmltype Object3D \inqmlmodule QtQuick3D \instantiates QQuick3DObject \inherits QtObject
void clear()
Definition qset.h:61
bool contains(const T &value) const
Definition qset.h:71
void start(Priority=InheritPriority)
Definition qthread.cpp:996
static int idealThreadCount() noexcept
Definition qthread.cpp:1049
bool wait(QDeadlineTimer deadline=QDeadlineTimer(QDeadlineTimer::Forever))
Definition qthread.cpp:1023
static void usleep(unsigned long)
void finished(QPrivateSignal)
void quit()
Definition qthread.cpp:1008
The QVector3D class represents a vector or vertex in 3D space.
Definition qvectornd.h:171
void frameDone(float deltaTime)
void frameDoneDesignStudio()
void simulateFrame(float minTimestep, float maxTimestep)
SimulationWorker(QPhysXWorld *physx)
void simulateFrameDesignStudio(float minTimestep, float maxTimestep)
QSet< QString >::iterator it
constexpr QColor lightsalmon
Definition qcolor.h:389
constexpr QColor cyan
Definition qcolor.h:334
constexpr QColor blueviolet
Definition qcolor.h:324
constexpr QColor chartreuse
Definition qcolor.h:328
constexpr QColor red
Definition qcolor.h:433
constexpr QColor black
Definition qcolor.h:321
QQuick3DGeometry * generateConvexMeshGeometry(physx::PxConvexMesh *convexMesh)
QQuick3DGeometry * generateSphereGeometry(const float radius)
QQuick3DGeometry * generateHeightFieldGeometry(physx::PxHeightField *heightField, float heightScale, float rowScale, float columnScale)
QQuick3DGeometry * generatePlaneGeometry()
QQuick3DGeometry * generateBoxGeometry(const QVector3D &halfExtents)
QQuick3DGeometry * generateCapsuleGeometry(const float radius, const float halfHeight)
QQuick3DGeometry * generateTriangleMeshGeometry(physx::PxTriangleMesh *triangleMesh)
Q_ALWAYS_INLINE physx::PxVec3 toPhysXType(const QVector3D &qvec)
const QQuaternion kMinus90YawRotation
Q_ALWAYS_INLINE QVector3D toQtType(const physx::PxVec3 &vec)
Combined button and popup list for selecting options.
static const QCssKnownValue positions[NumKnownPositionModes - 1]
static Q_CONSTINIT QBasicAtomicInt running
bool qFuzzyCompare(qfloat16 p1, qfloat16 p2) noexcept
Definition qfloat16.h:333
#define qWarning
Definition qlogging.h:166
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
static ControlElement< T > * ptr(QWidget *widget)
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:42
GLenum GLuint GLenum GLsizei length
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLuint color
[2]
static QWorldManager worldManager
static void collectPhysicsNodes(QQuick3DObject *node, QList< QAbstractPhysicsNode * > &nodes)
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
Q_CORE_EXPORT bool qEnvironmentVariableIsEmpty(const char *varName) noexcept
#define Q_OBJECT
#define slots
#define signals
#define emit
#define Q_UNUSED(x)
ptrdiff_t qsizetype
Definition qtypes.h:165
static const uint base
Definition qurlidna.cpp:20
QSqlQueryModel * model
[16]
view viewport() -> scroll(dx, dy, deviceRect)
QLayoutItem * child
[0]
QLatin1StringView world("world")
QVector< QAbstractPhysicsNode * > orphanNodes
QVector< QPhysicsWorld * > worlds
static StaticPhysXObjects & getReference()