Qt 6.x
The Qt SDK
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 "qcapsuleshape_p.h"
6#include "qphysicsworld_p.h"
7
9#include "qphysicsutils_p.h"
10#include "qtriggerbody_p.h"
11#include "qrigidbody_p.h"
12#include "qplaneshape_p.h"
13#include "qphysicscommands_p.h"
14
15#include "PxPhysicsAPI.h"
17#include "qheightfieldshape_p.h"
18
19#include "cooking/PxCooking.h"
20
21#include "extensions/PxDefaultCpuDispatcher.h"
22
23#include <QtQuick3D/private/qquick3dobject_p.h>
24#include <QtQuick3D/private/qquick3dnode_p.h>
25#include <QtQuick3D/private/qquick3dmodel_p.h>
26#include <QtQuick3D/private/qquick3ddefaultmaterial_p.h>
27#include <QtQuick3DUtils/private/qssgutils_p.h>
28
29#define PHYSX_ENABLE_PVD 0
30
32
144Q_LOGGING_CATEGORY(lcQuick3dPhysics, "qt.quick3d.physics");
145
147
148static inline bool fuzzyEquals(const physx::PxTransform &a, const physx::PxTransform &b)
149{
150 return qFuzzyCompare(a.p.x, b.p.x) && qFuzzyCompare(a.p.y, b.p.y) && qFuzzyCompare(a.p.z, b.p.z)
151 && qFuzzyCompare(a.q.x, b.q.x) && qFuzzyCompare(a.q.y, b.q.y)
152 && qFuzzyCompare(a.q.z, b.q.z) && qFuzzyCompare(a.q.w, b.q.w);
153}
154
155static physx::PxTransform getPhysXWorldTransform(const QQuick3DNode *node)
156{
157 const QQuaternion &rotation = node->sceneRotation();
158 const QVector3D worldPosition = node->scenePosition();
159 return physx::PxTransform(QPhysicsUtils::toPhysXType(worldPosition),
161}
162
163static physx::PxTransform getPhysXWorldTransform(const QMatrix4x4 transform)
164{
165 auto rotationMatrix = transform;
166 mat44::normalize(rotationMatrix);
167 auto rotation =
169 const QVector3D worldPosition = mat44::getPosition(transform);
170 return physx::PxTransform(QPhysicsUtils::toPhysXType(worldPosition),
172}
173
174static physx::PxTransform getPhysXLocalTransform(const QQuick3DNode *node)
175{
176 // Modify transforms to make the PhysX shapes match the QtQuick3D conventions
177 if (qobject_cast<const QPlaneShape *>(node) != nullptr) {
178 // Rotate the plane to make it match the built-in rectangle
179 const QQuaternion rotation = kMinus90YawRotation * node->rotation();
180 return physx::PxTransform(QPhysicsUtils::toPhysXType(node->position()),
182 } else if (auto *hf = qobject_cast<const QHeightFieldShape *>(node)) {
183 // Shift the height field so it's centered at the origin
184 return physx::PxTransform(QPhysicsUtils::toPhysXType(node->position() + hf->hfOffset()),
186 }
187
188 const QQuaternion &rotation = node->rotation();
189 const QVector3D &localPosition = node->position();
190 const QVector3D &scale = node->sceneScale();
191 return physx::PxTransform(QPhysicsUtils::toPhysXType(localPosition * scale),
193}
194
195static physx::PxFilterFlags
196contactReportFilterShader(physx::PxFilterObjectAttributes /*attributes0*/,
197 physx::PxFilterData /*filterData0*/,
198 physx::PxFilterObjectAttributes /*attributes1*/,
199 physx::PxFilterData /*filterData1*/, physx::PxPairFlags &pairFlags,
200 const void * /*constantBlock*/, physx::PxU32 /*constantBlockSize*/)
201{
202 // Makes objects collide
203 const auto defaultCollisonFlags =
204 physx::PxPairFlag::eSOLVE_CONTACT | physx::PxPairFlag::eDETECT_DISCRETE_CONTACT;
205
206 // For trigger body detection
207 const auto notifyTouchFlags =
208 physx::PxPairFlag::eNOTIFY_TOUCH_FOUND | physx::PxPairFlag::eNOTIFY_TOUCH_LOST;
209
210 // For contact detection
211 const auto notifyContactFlags = physx::PxPairFlag::eNOTIFY_CONTACT_POINTS;
212
213 pairFlags = defaultCollisonFlags | notifyTouchFlags | notifyContactFlags;
214 return physx::PxFilterFlag::eDEFAULT;
215}
216
217static physx::PxFilterFlags
218contactReportFilterShaderCCD(physx::PxFilterObjectAttributes /*attributes0*/,
219 physx::PxFilterData /*filterData0*/,
220 physx::PxFilterObjectAttributes /*attributes1*/,
221 physx::PxFilterData /*filterData1*/, physx::PxPairFlags &pairFlags,
222 const void * /*constantBlock*/, physx::PxU32 /*constantBlockSize*/)
223{
224 // Makes objects collide
225 const auto defaultCollisonFlags = physx::PxPairFlag::eSOLVE_CONTACT
226 | physx::PxPairFlag::eDETECT_DISCRETE_CONTACT | physx::PxPairFlag::eDETECT_CCD_CONTACT;
227
228 // For trigger body detection
229 const auto notifyTouchFlags =
230 physx::PxPairFlag::eNOTIFY_TOUCH_FOUND | physx::PxPairFlag::eNOTIFY_TOUCH_LOST;
231
232 // For contact detection
233 const auto notifyContactFlags = physx::PxPairFlag::eNOTIFY_CONTACT_POINTS;
234
235 pairFlags = defaultCollisonFlags | notifyTouchFlags | notifyContactFlags;
236 return physx::PxFilterFlag::eDEFAULT;
237}
238
239class SimulationEventCallback : public physx::PxSimulationEventCallback
240{
241public:
242 SimulationEventCallback(QPhysicsWorld *worldIn) : world(worldIn) {};
243 virtual ~SimulationEventCallback() = default;
244
245 void onTrigger(physx::PxTriggerPair *pairs, physx::PxU32 count) override
246 {
247 QMutexLocker locker(&world->m_removedPhysicsNodesMutex);
248
249 for (physx::PxU32 i = 0; i < count; i++) {
250 // ignore pairs when shapes have been deleted
251 if (pairs[i].flags
252 & (physx::PxTriggerPairFlag::eREMOVED_SHAPE_TRIGGER
253 | physx::PxTriggerPairFlag::eREMOVED_SHAPE_OTHER))
254 continue;
255
256 QTriggerBody *triggerNode =
257 static_cast<QTriggerBody *>(pairs[i].triggerActor->userData);
258
259 QAbstractPhysicsNode *otherNode =
260 static_cast<QAbstractPhysicsNode *>(pairs[i].otherActor->userData);
261
262 if (!triggerNode || !otherNode) {
263 qWarning() << "QtQuick3DPhysics internal error: null pointer in trigger collision.";
264 continue;
265 }
266
267 if (world->isNodeRemoved(triggerNode) || world->isNodeRemoved(otherNode))
268 continue;
269
270 if (pairs->status == physx::PxPairFlag::eNOTIFY_TOUCH_FOUND) {
271 if (otherNode->sendTriggerReports()) {
272 triggerNode->registerCollision(otherNode);
273 }
274 if (otherNode->receiveTriggerReports()) {
275 emit otherNode->enteredTriggerBody(triggerNode);
276 }
277 } else if (pairs->status == physx::PxPairFlag::eNOTIFY_TOUCH_LOST) {
278 if (otherNode->sendTriggerReports()) {
279 triggerNode->deregisterCollision(otherNode);
280 }
281 if (otherNode->receiveTriggerReports()) {
282 emit otherNode->exitedTriggerBody(triggerNode);
283 }
284 }
285 }
286 }
287
288 void onConstraintBreak(physx::PxConstraintInfo * /*constraints*/,
289 physx::PxU32 /*count*/) override {};
290 void onWake(physx::PxActor ** /*actors*/, physx::PxU32 /*count*/) override {};
291 void onSleep(physx::PxActor ** /*actors*/, physx::PxU32 /*count*/) override {};
292 void onContact(const physx::PxContactPairHeader &pairHeader, const physx::PxContactPair *pairs,
293 physx::PxU32 nbPairs) override
294 {
295 QMutexLocker locker(&world->m_removedPhysicsNodesMutex);
296 constexpr physx::PxU32 bufferSize = 64;
297 physx::PxContactPairPoint contacts[bufferSize];
298
299 for (physx::PxU32 i = 0; i < nbPairs; i++) {
300 const physx::PxContactPair &contactPair = pairs[i];
301
302 if (contactPair.events & physx::PxPairFlag::eNOTIFY_TOUCH_FOUND) {
303 QAbstractPhysicsNode *trigger =
304 static_cast<QAbstractPhysicsNode *>(pairHeader.actors[0]->userData);
306 static_cast<QAbstractPhysicsNode *>(pairHeader.actors[1]->userData);
307
308 if (!trigger || !other || !trigger->m_backendObject || !other->m_backendObject
309 || world->isNodeRemoved(trigger) || world->isNodeRemoved(other))
310 continue;
311
312 const bool triggerReceive =
313 trigger->receiveContactReports() && other->sendContactReports();
314 const bool otherReceive =
315 other->receiveContactReports() && trigger->sendContactReports();
316
317 if (!triggerReceive && !otherReceive)
318 continue;
319
320 physx::PxU32 nbContacts = pairs[i].extractContacts(contacts, bufferSize);
321
323 QList<QVector3D> impulses;
324 QList<QVector3D> normals;
325
326 positions.reserve(nbContacts);
327 impulses.reserve(nbContacts);
328 normals.reserve(nbContacts);
329
330 for (physx::PxU32 j = 0; j < nbContacts; j++) {
331 physx::PxVec3 position = contacts[j].position;
332 physx::PxVec3 impulse = contacts[j].impulse;
333 physx::PxVec3 normal = contacts[j].normal;
334
336 impulses.push_back(QPhysicsUtils::toQtType(impulse));
337 normals.push_back(QPhysicsUtils::toQtType(normal));
338 }
339
340 QList<QVector3D> normalsInverted;
341 normalsInverted.reserve(normals.size());
342 for (const QVector3D &v : normals) {
343 normalsInverted.push_back(QVector3D(-v.x(), -v.y(), -v.z()));
344 }
345
346 if (triggerReceive)
347 trigger->registerContact(other, positions, impulses, normals);
348 if (otherReceive)
349 other->registerContact(trigger, positions, impulses, normalsInverted);
350 }
351 }
352 };
353 void onAdvance(const physx::PxRigidBody *const * /*bodyBuffer*/,
354 const physx::PxTransform * /*poseBuffer*/,
355 const physx::PxU32 /*count*/) override {};
356
357private:
358 QPhysicsWorld *world = nullptr;
359};
360
361class ControllerCallback : public physx::PxUserControllerHitReport
362{
363public:
364 ControllerCallback(QPhysicsWorld *worldIn) : world(worldIn) { }
365
366 void onShapeHit(const physx::PxControllerShapeHit &hit) override
367 {
368 QMutexLocker locker(&world->m_removedPhysicsNodesMutex);
369
370 QAbstractPhysicsNode *other = static_cast<QAbstractPhysicsNode *>(hit.actor->userData);
371 QCharacterController *trigger =
372 static_cast<QCharacterController *>(hit.controller->getUserData());
373
374 if (!trigger || !other || !trigger->enableShapeHitCallback())
375 return;
376
377 QVector3D position = QPhysicsUtils::toQtType(physx::toVec3(hit.worldPos));
378 QVector3D impulse = QPhysicsUtils::toQtType(hit.dir * hit.length);
379 QVector3D normal = QPhysicsUtils::toQtType(hit.worldNormal);
380
381 emit trigger->shapeHit(other, position, impulse, normal);
382 }
383 void onControllerHit(const physx::PxControllersHit & /*hit*/) override { }
384 void onObstacleHit(const physx::PxControllerObstacleHit & /*hit*/) override { }
385
386private:
387 QPhysicsWorld *world = nullptr;
388};
389
390#define PHYSX_RELEASE(x) \
391 if (x != nullptr) { \
392 x->release(); \
393 x = nullptr; \
394 }
395
397{
398 physx::PxDefaultErrorCallback defaultErrorCallback;
399 physx::PxDefaultAllocator defaultAllocatorCallback;
400 physx::PxFoundation *foundation = nullptr;
401 physx::PxPvd *pvd = nullptr;
402 physx::PxPvdTransport *transport = nullptr;
403 physx::PxPhysics *physics = nullptr;
404 physx::PxDefaultCpuDispatcher *dispatcher = nullptr;
405 physx::PxCooking *cooking = nullptr;
406
407 unsigned int foundationRefCount = 0;
408 bool foundationCreated = false;
409 bool physicsCreated = false;
410};
411
413
415{
417 {
419
421 return;
422
423 s_physx.foundation = PxCreateFoundation(
425 if (!s_physx.foundation)
426 qFatal("PxCreateFoundation failed!");
427
429
430#if PHYSX_ENABLE_PVD
431 s_physx.pvd = PxCreatePvd(*m_physx->foundation);
432 s_physx.transport = physx::PxDefaultPvdSocketTransportCreate("qt", 5425, 10);
433 s_physx.pvd->connect(*m_physx->transport, physx::PxPvdInstrumentationFlag::eALL);
434#endif
435
436 // FIXME: does the tolerance matter?
437 s_physx.cooking = PxCreateCooking(PX_PHYSICS_VERSION, *s_physx.foundation,
438 physx::PxCookingParams(physx::PxTolerancesScale()));
439 }
440
442 {
444 if (s_physx.foundationRefCount == 0) {
453
454 delete callback;
455 callback = nullptr;
457 s_physx.physicsCreated = false;
458 } else {
459 delete callback;
460 callback = nullptr;
463 }
464 }
465
466 void createScene(float typicalLength, float typicalSpeed, const QVector3D &gravity,
467 bool enableCCD, QPhysicsWorld *physicsWorld)
468 {
469 if (scene) {
470 qWarning() << "Scene already created";
471 return;
472 }
473
474 physx::PxTolerancesScale scale;
475 scale.length = typicalLength;
476 scale.speed = typicalSpeed;
477
478 if (!s_physx.physicsCreated) {
479 constexpr bool recordMemoryAllocations = true;
480 s_physx.physics = PxCreatePhysics(PX_PHYSICS_VERSION, *s_physx.foundation, scale,
481 recordMemoryAllocations, s_physx.pvd);
482 if (!s_physx.physics)
483 qFatal("PxCreatePhysics failed!");
484 s_physx.dispatcher = physx::PxDefaultCpuDispatcherCreate(2);
485 s_physx.physicsCreated = true;
486 }
487
488 callback = new SimulationEventCallback(physicsWorld);
489
490 physx::PxSceneDesc sceneDesc(scale);
491 sceneDesc.gravity = QPhysicsUtils::toPhysXType(gravity);
492 sceneDesc.cpuDispatcher = s_physx.dispatcher;
493
494 if (enableCCD) {
495 sceneDesc.filterShader = contactReportFilterShaderCCD;
496 sceneDesc.flags |= physx::PxSceneFlag::eENABLE_CCD;
497 } else {
498 sceneDesc.filterShader = contactReportFilterShader;
499 }
500 sceneDesc.solverType = physx::PxSolverType::eTGS;
501 sceneDesc.simulationEventCallback = callback;
502
503 scene = s_physx.physics->createScene(sceneDesc);
504 }
505
506 // variables unique to each world/scene
507 physx::PxControllerManager *controllerManager = nullptr;
509 physx::PxScene *scene = nullptr;
510 bool isRunning = false;
511};
512
513// Used for debug drawing
515 Static = 0,
516 DynamicAwake = 1,
517 DynamicSleeping = 2,
518 Trigger = 3,
519 Unknown = 4
520};
521
523{
524public:
526 {
527 node->m_backendObject = this;
528 }
530
531 bool cleanupIfRemoved(PhysXWorld *physX); // TODO rename??
532
533 virtual void init(QPhysicsWorld *world, PhysXWorld *physX) = 0;
534 virtual void updateDefaultDensity(float /*density*/) { }
535 virtual void createMaterial(PhysXWorld *physX);
537 virtual void markDirtyShapes() { }
539
540 virtual void sync(float deltaTime, QHash<QQuick3DNode *, QMatrix4x4> &transformCache) = 0;
541 virtual void cleanup(PhysXWorld *)
542 {
543 for (auto *shape : shapes)
544 PHYSX_RELEASE(shape);
547 }
548 virtual bool debugGeometryCapability() { return false; }
549 virtual physx::PxTransform getGlobalPose() { return {}; }
550
551 virtual bool useTriggerFlag() { return false; }
553
554 bool shapesDirty() const { return frontendNode && frontendNode->m_shapesDirty; }
555 void setShapesDirty(bool dirty) { frontendNode->m_shapesDirty = dirty; }
556
558 physx::PxMaterial *material = nullptr;
560 bool isRemoved = false;
561 static physx::PxMaterial *defaultMaterial;
562};
563
564physx::PxMaterial *QAbstractPhysXNode::defaultMaterial = nullptr;
565
567{
568 if (isRemoved) {
569 cleanup(physX);
570 delete this;
571 return true;
572 }
573 return false;
574}
575
577{
578public:
580 void cleanup(PhysXWorld *physX) override;
581 void init(QPhysicsWorld *world, PhysXWorld *physX) override;
582
583 void sync(float deltaTime, QHash<QQuick3DNode *, QMatrix4x4> &transformCache) override;
584 void createMaterial(PhysXWorld *physX) override;
585
586private:
587 physx::PxCapsuleController *controller = nullptr;
588 ControllerCallback *reportCallback = nullptr;
589};
590
592{
593public:
595 void cleanup(PhysXWorld *physX) override
596 {
597 if (actor) {
598 physX->scene->removeActor(*actor);
600 }
602 }
603 void init(QPhysicsWorld *world, PhysXWorld *physX) override;
604 void sync(float deltaTime, QHash<QQuick3DNode *, QMatrix4x4> &transformCache) override;
605 void markDirtyShapes() override;
606 void rebuildDirtyShapes(QPhysicsWorld *world, PhysXWorld *physX) override;
607 virtual void createActor(PhysXWorld *physX);
608
609 bool debugGeometryCapability() override { return true; }
610 physx::PxTransform getGlobalPose() override { return actor->getGlobalPose(); }
611 void buildShapes(PhysXWorld *physX);
612
613 physx::PxRigidActor *actor = nullptr;
614};
615
617{
618public:
620 void createMaterial(PhysXWorld *physX) override;
621};
622
624{
625public:
627
629 void sync(float deltaTime, QHash<QQuick3DNode *, QMatrix4x4> &transformCache) override;
630 void createActor(PhysXWorld *physX) override;
631};
632
634{
635public:
637
639 void sync(float deltaTime, QHash<QQuick3DNode *, QMatrix4x4> &transformCache) override;
640 void rebuildDirtyShapes(QPhysicsWorld *world, PhysXWorld *physX) override;
641 void updateDefaultDensity(float density) override;
642};
643
645{
646public:
648
650 void sync(float deltaTime, QHash<QQuick3DNode *, QMatrix4x4> &transformCache) override;
651 bool useTriggerFlag() override { return true; }
652};
653
655{
656public:
658 { // TODO: virtual function in QAbstractPhysicsNode??
659
660 if (auto *rigidBody = qobject_cast<QDynamicRigidBody *>(node))
661 return new QPhysXDynamicBody(rigidBody);
662 if (auto *staticBody = qobject_cast<QStaticRigidBody *>(node))
663 return new QPhysXStaticBody(staticBody);
664 if (auto *triggerBody = qobject_cast<QTriggerBody *>(node))
665 return new QPhysXTriggerBody(triggerBody);
666 if (auto *controller = qobject_cast<QCharacterController *>(node))
667 return new QPhysXCharacterController(controller);
668 Q_UNREACHABLE();
669 }
670};
671
672/*
673 NOTE
674 The inheritance hierarchy is not ideal, since both controller and rigid body have materials,
675 but trigger doesn't. AND both trigger and rigid body have actors, but controller doesn't.
676
677 TODO: defaultMaterial isn't used for rigid bodies, since they always create their own
678 QPhysicsMaterial with default values. We should only have a qt material when set explicitly.
679 */
680
682 QPhysicsMaterial *qtMaterial)
683{
684 if (qtMaterial) {
685 material = s_physx.physics->createMaterial(qtMaterial->staticFriction(),
686 qtMaterial->dynamicFriction(),
687 qtMaterial->restitution());
688 } else {
689 if (!defaultMaterial) {
690 defaultMaterial = s_physx.physics->createMaterial(
693 }
695 }
696}
697
699{
700 createMaterialFromQtMaterial(physX, nullptr);
701}
702
704{
705 createMaterialFromQtMaterial(physX, static_cast<QCharacterController *>(frontendNode)->physicsMaterial());
706}
707
709{
710 createMaterialFromQtMaterial(physX, static_cast<QAbstractPhysicsBody *>(frontendNode)->physicsMaterial());
711}
712
714{
715 physx::PxTransform trf = getPhysXWorldTransform(frontendNode);
716 actor = s_physx.physics->createRigidDynamic(trf);
717}
718
720{
721 physx::PxTransform trf = getPhysXWorldTransform(frontendNode);
722 actor = s_physx.physics->createRigidStatic(trf);
723}
724
726{
727 Q_ASSERT(!actor);
728
729 createMaterial(physX);
730 createActor(physX);
731
732 actor->userData = reinterpret_cast<void *>(frontendNode);
733 physX->scene->addActor(*actor);
734 setShapesDirty(true);
735}
736
738{
739 PHYSX_RELEASE(controller);
740 delete reportCallback;
741 reportCallback = nullptr;
743}
744
746{
747 Q_ASSERT(!controller);
748
749 auto *characterController = static_cast<QCharacterController *>(frontendNode);
750
751 auto shapes = characterController->getCollisionShapesList();
752 if (shapes.length() != 1) {
753 qWarning() << "CharacterController: invalid collision shapes list.";
754 return;
755 }
756 auto *capsule = qobject_cast<QCapsuleShape *>(shapes.first());
757 if (!capsule) {
758 qWarning() << "CharacterController: collision shape is not a capsule.";
759 return;
760 }
761 auto *mgr = world->controllerManager();
762 if (!mgr) {
763 qWarning() << "QtQuick3DPhysics internal error: missing controller manager.";
764 return;
765 }
766
767 createMaterial(physX);
768
769 const QVector3D scale = characterController->sceneScale();
770 const qreal heightScale = scale.y();
771 const qreal radiusScale = scale.x();
772 physx::PxCapsuleControllerDesc desc;
773 reportCallback = new ControllerCallback(world);
774 desc.reportCallback = reportCallback;
775 desc.radius = 0.5f * radiusScale * capsule->diameter();
776 desc.height = heightScale * capsule->height();
777 desc.stepOffset = 0.25f * desc.height; // TODO: API
778
779 desc.material = material;
780 const QVector3D pos = characterController->scenePosition();
781 desc.position = { pos.x(), pos.y(), pos.z() };
782 // Safe to static_cast since createController will always return a PxCapsuleController
783 // if desc is of type PxCapsuleControllerDesc
784 controller = static_cast<physx::PxCapsuleController *>(mgr->createController(desc));
785
786 if (!controller) {
787 qWarning() << "QtQuick3DPhysics internal error: could not create controller.";
788 return;
789 }
790
791 controller->setUserData(static_cast<void *>(frontendNode));
792
793 auto *actor = controller->getActor();
794 if (actor)
795 actor->userData = characterController;
796 else
797 qWarning() << "QtQuick3DPhysics internal error: CharacterController created without actor.";
798}
799
801{
802 QDynamicRigidBody *rigidBody = static_cast<QDynamicRigidBody *>(frontendNode);
803 rigidBody->updateDefaultDensity(density);
804}
805
807{
808 auto dynamicActor = static_cast<physx::PxRigidDynamic *>(actor);
809 return dynamicActor->isSleeping() ? DebugDrawBodyType::DynamicSleeping
811}
812
814{
815 if (!frontendNode || !actor)
816 return;
817
818 // Go through the shapes and look for a change in pose (rotation, position)
819 // TODO: it is likely cheaper to connect a signal for changes on the position and rotation
820 // property and mark the node dirty then.
821 if (!shapesDirty()) {
822 const auto &collisionShapes = frontendNode->getCollisionShapesList();
823 const auto &physXShapes = shapes;
824
825 const int len = collisionShapes.size();
826 if (physXShapes.size() != len) {
827 // This should not really happen but check it anyway
828 setShapesDirty(true);
829 } else {
830
831 for (int i = 0; i < len; i++) {
832 auto poseNew = getPhysXLocalTransform(collisionShapes[i]);
833 auto poseOld = physXShapes[i]->getLocalPose();
834
835 if (!fuzzyEquals(poseNew, poseOld)) {
836 setShapesDirty(true);
837 break;
838 }
839 }
840 }
841 }
842}
843
845{
846 auto body = actor;
847 for (auto *shape : shapes) {
848 body->detachShape(*shape);
849 PHYSX_RELEASE(shape);
850 }
851
852 // TODO: Only remove changed shapes?
853 shapes.clear();
854
855 for (const auto &collisionShape : frontendNode->getCollisionShapesList()) {
856 // TODO: shapes can be shared between multiple actors.
857 // Do we need to create new ones for every body?
858 auto *geom = collisionShape->getPhysXGeometry();
859 if (!geom || !material)
860 continue;
861 auto physXShape = s_physx.physics->createShape(*geom, *material);
862
863 if (useTriggerFlag()) {
864 physXShape->setFlag(physx::PxShapeFlag::eSIMULATION_SHAPE, false);
865 physXShape->setFlag(physx::PxShapeFlag::eTRIGGER_SHAPE, true);
866 }
867
868 shapes.push_back(physXShape);
869 physXShape->setLocalPose(getPhysXLocalTransform(collisionShape));
870 body->attachShape(*physXShape);
871 }
872}
873
875{
876 if (!shapesDirty())
877 return;
878 buildShapes(physX);
879 setShapesDirty(false);
880}
881
883{
884 if (!shapesDirty())
885 return;
886
887 buildShapes(physX);
888
889 QDynamicRigidBody *drb = static_cast<QDynamicRigidBody *>(frontendNode);
890
891 // Density must be set after shapes so the inertia tensor is set
892 if (!drb->hasStaticShapes()) {
893 // Body with only dynamic shapes, set/calculate mass
894 QPhysicsCommand *command = nullptr;
895 switch (drb->massMode()) {
897 command = new QPhysicsCommandSetDensity(world->defaultDensity());
898 break;
899 }
901 command = new QPhysicsCommandSetDensity(drb->density());
902 break;
903 }
905 const float mass = qMax(drb->mass(), 0.f);
906 command = new QPhysicsCommandSetMass(mass);
907 break;
908 }
910 const float mass = qMax(drb->mass(), 0.f);
911 command = new QPhysicsCommandSetMassAndInertiaTensor(mass, drb->inertiaTensor());
912 break;
913 }
915 const float mass = qMax(drb->mass(), 0.f);
916 command = new QPhysicsCommandSetMassAndInertiaMatrix(mass, drb->inertiaMatrix());
917 break;
918 }
919 }
920
921 drb->commandQueue().enqueue(command);
922 } else if (!drb->isKinematic()) {
923 // Body with static shapes that is not kinematic, this is disallowed
924 qWarning() << "Cannot make body containing trimesh/heightfield/plane non-kinematic, "
925 "forcing kinematic.";
926 drb->setIsKinematic(true);
927 }
928
929 auto *dynamicBody = static_cast<physx::PxRigidDynamic *>(actor);
930 dynamicBody->setRigidBodyFlag(physx::PxRigidBodyFlag::eKINEMATIC, drb->isKinematic());
931
932 if (world->enableCCD() && !drb->isKinematic()) // CCD not supported for kinematic bodies
933 dynamicBody->setRigidBodyFlag(physx::PxRigidBodyFlag::eENABLE_CCD, true);
934
935 setShapesDirty(false);
936}
937
939 const QDynamicRigidBody &rigidBody, physx::PxRigidBody &body)
940{
941 for (auto command : commandQueue) {
942 command->execute(rigidBody, body);
943 delete command;
944 }
945
946 commandQueue.clear();
947}
948
950 QHash<QQuick3DNode *, QMatrix4x4> &transformCache)
951{
952 // already calculated transform
953 if (transformCache.contains(node))
954 return transformCache[node];
955
956 QMatrix4x4 localTransform;
957
958 // DynamicRigidBody vs StaticRigidBody use different values for calculating the local transform
959 if (auto drb = qobject_cast<const QDynamicRigidBody *>(node); drb != nullptr) {
960 if (!drb->isKinematic()) {
961 qWarning() << "Non-kinematic body as a parent of a kinematic body is unsupported";
962 }
964 drb->kinematicPosition(), drb->scale(), drb->kinematicPivot(), drb->kinematicRotation());
965 } else {
966 localTransform = QSSGRenderNode::calculateTransformMatrix(node->position(), node->scale(),
967 node->pivot(), node->rotation());
968 }
969
970 QQuick3DNode *parent = node->parentNode();
971 if (!parent) // no parent, local transform is scene transform
972 return localTransform;
973
974 // calculate the parent scene transform and apply the nodes local transform
975 QMatrix4x4 parentTransform = calculateKinematicNodeTransform(parent, transformCache);
976 QMatrix4x4 sceneTransform = parentTransform * localTransform;
977
978 transformCache[node] = sceneTransform;
979 return sceneTransform;
980}
981
982static physx::PxRigidDynamicLockFlags getLockFlags(QDynamicRigidBody *body)
983{
984 const auto lockAngular = body->angularAxisLock();
985 const auto lockLinear = body->linearAxisLock();
986 const int flags = (lockAngular & QDynamicRigidBody::AxisLock::LockX
987 ? physx::PxRigidDynamicLockFlag::eLOCK_ANGULAR_X
988 : 0)
990 ? physx::PxRigidDynamicLockFlag::eLOCK_ANGULAR_Y
991 : 0)
993 ? physx::PxRigidDynamicLockFlag::eLOCK_ANGULAR_Z
994 : 0)
996 ? physx::PxRigidDynamicLockFlag::eLOCK_LINEAR_X
997 : 0)
999 ? physx::PxRigidDynamicLockFlag::eLOCK_LINEAR_Y
1000 : 0)
1002 ? physx::PxRigidDynamicLockFlag::eLOCK_LINEAR_Z
1003 : 0);
1004 return static_cast<physx::PxRigidDynamicLockFlags>(flags);
1005}
1006
1007static void updatePhysXMaterial(const QPhysicsMaterial *qtMaterial,
1008 physx::PxMaterial *physXMaterial)
1009{
1010 const float staticFriction = qtMaterial->staticFriction();
1011 const float dynamicFriction = qtMaterial->dynamicFriction();
1012 const float restitution = qtMaterial->restitution();
1013 if (physXMaterial->getStaticFriction() != staticFriction)
1014 physXMaterial->setStaticFriction(staticFriction);
1015 if (physXMaterial->getDynamicFriction() != dynamicFriction)
1016 physXMaterial->setDynamicFriction(dynamicFriction);
1017 if (physXMaterial->getRestitution() != restitution)
1018 physXMaterial->setRestitution(restitution);
1019}
1020
1021void QPhysXActorBody::sync(float /*deltaTime*/, QHash<QQuick3DNode *, QMatrix4x4> & /*transformCache*/)
1022{
1023 auto *body = static_cast<QAbstractPhysicsBody *>(frontendNode);
1024 if (QPhysicsMaterial *qtMaterial = body->physicsMaterial()) {
1025 updatePhysXMaterial(qtMaterial, material);
1026 }
1027}
1028
1030{
1032}
1033
1034void QPhysXTriggerBody::sync(float /*deltaTime*/, QHash<QQuick3DNode *, QMatrix4x4> & /*transformCache*/)
1035{
1036 auto *triggerBody = static_cast<QTriggerBody *>(frontendNode);
1037 actor->setGlobalPose(getPhysXWorldTransform(triggerBody));
1038}
1039
1041{
1042 auto *dynamicRigidBody = static_cast<QDynamicRigidBody *>(frontendNode);
1043 // first update front end node from physx simulation
1044 dynamicRigidBody->updateFromPhysicsTransform(actor->getGlobalPose());
1045
1046 auto *dynamicActor = static_cast<physx::PxRigidDynamic *>(actor);
1047 processCommandQueue(dynamicRigidBody->commandQueue(), *dynamicRigidBody, *dynamicActor);
1048 if (dynamicRigidBody->isKinematic()) {
1049 // Since this is a kinematic body we need to calculate the transform by hand and since
1050 // bodies can occur in other bodies we need to calculate the tranform recursively for all
1051 // parents. To save some computation we cache these transforms in 'transformCache'.
1052 QMatrix4x4 transform = calculateKinematicNodeTransform(dynamicRigidBody, transformCache);
1053 dynamicActor->setKinematicTarget(getPhysXWorldTransform(transform));
1054 } else {
1055 dynamicActor->setRigidDynamicLockFlags(getLockFlags(dynamicRigidBody));
1056 }
1057 QPhysXActorBody::sync(deltaTime, transformCache);
1058}
1059
1061 QHash<QQuick3DNode *, QMatrix4x4> & /*transformCache*/)
1062{
1063 if (controller == nullptr)
1064 return;
1065
1066 auto *characterController = static_cast<QCharacterController *>(frontendNode);
1067
1068 // Update capsule height, radius, stepOffset
1069 const auto &shapes = characterController->getCollisionShapesList();
1070 auto capsule = shapes.length() == 1 ? qobject_cast<QCapsuleShape *>(shapes.front()) : nullptr;
1071
1072 if (shapes.length() != 1) {
1073 qWarning() << "CharacterController: invalid collision shapes list.";
1074 } else if (!capsule) {
1075 qWarning() << "CharacterController: collision shape is not a capsule.";
1076 } else {
1077 const QVector3D sceneScale = characterController->sceneScale();
1078 const qreal heightScale = sceneScale.y();
1079 const qreal radiusScale = sceneScale.x();
1080
1081 // Update height
1082 const float heightNew = heightScale * capsule->height();
1083 if (!qFuzzyCompare(controller->getHeight(), heightNew))
1084 controller->resize(heightNew);
1085 // Update radius
1086 const float radiusNew = 0.5f * radiusScale * capsule->diameter();
1087 if (!qFuzzyCompare(controller->getRadius(), radiusNew))
1088 controller->setRadius(radiusNew);
1089 // Update stepOffset
1090 const float stepOffsetNew = 0.25f * heightNew;
1091 if (!qFuzzyCompare(controller->getStepOffset(), stepOffsetNew))
1092 controller->setStepOffset(stepOffsetNew);
1093 }
1094
1095 // update node from physX
1096 QVector3D position = QPhysicsUtils::toQtType(physx::toVec3(controller->getPosition()));
1097 const QQuick3DNode *parentNode = static_cast<QQuick3DNode *>(characterController->parentItem());
1098 if (!parentNode) {
1099 // then it is the same space
1100 characterController->setPosition(position);
1101 } else {
1102 characterController->setPosition(parentNode->mapPositionFromScene(position));
1103 }
1104
1105 QVector3D teleportPos;
1106 bool teleport = characterController->getTeleport(teleportPos);
1107 if (teleport) {
1108 controller->setPosition({ teleportPos.x(), teleportPos.y(), teleportPos.z() });
1109 } else if (deltaTime > 0) {
1110 const auto displacement = QPhysicsUtils::toPhysXType(characterController->getDisplacement(deltaTime));
1111 auto collisions =
1112 controller->move(displacement, displacement.magnitude() / 100, deltaTime, {});
1113 characterController->setCollisions(QCharacterController::Collisions(uint(collisions)));
1114 }
1115 // QCharacterController has a material property, but we don't inherit from
1116 // QPhysXMaterialBody, so we create the material manually in init()
1117 // TODO: handle material changes
1118}
1119
1121{
1123}
1124
1125void QPhysXStaticBody::sync(float deltaTime, QHash<QQuick3DNode *, QMatrix4x4> &transformCache)
1126{
1127 auto *staticBody = static_cast<QStaticRigidBody *>(frontendNode);
1128 const physx::PxTransform poseNew = getPhysXWorldTransform(staticBody);
1129 const physx::PxTransform poseOld = actor->getGlobalPose();
1130
1131 // For performance we only update static objects if they have been moved
1132 if (!fuzzyEquals(poseNew, poseOld))
1133 actor->setGlobalPose(poseNew);
1134 QPhysXActorBody::sync(deltaTime, transformCache);
1135}
1136
1138
1140{
1141 Q_OBJECT
1142public:
1144
1145public slots:
1146 void simulateFrame(float minTimestep, float maxTimestep)
1147 {
1148 if (!m_physx->isRunning) {
1149 m_timer.start();
1150 m_physx->isRunning = true;
1151 }
1152
1153 // Assuming: 0 <= minTimestep <= maxTimestep
1154
1155 constexpr auto MILLIONTH = 0.000001;
1156
1157 // If not enough time has elapsed we sleep until it has
1158 auto deltaMS = m_timer.nsecsElapsed() * MILLIONTH;
1159 while (deltaMS < minTimestep) {
1160 auto sleepUSecs = (minTimestep - deltaMS) * 1000.f;
1161 QThread::usleep(sleepUSecs);
1162 deltaMS = m_timer.nsecsElapsed() * MILLIONTH;
1163 }
1164 m_timer.restart();
1165
1166 auto deltaSecs = qMin(float(deltaMS), maxTimestep) * 0.001f;
1167 m_physx->scene->simulate(deltaSecs);
1168 m_physx->scene->fetchResults(true);
1169
1170 emit frameDone(deltaSecs);
1171 }
1172
1173signals:
1174 void frameDone(float deltaTime);
1175
1176private:
1177 PhysXWorld *m_physx = nullptr;
1178 QElapsedTimer m_timer;
1179};
1180
1182
1184{
1187};
1188
1190
1192{
1193 auto world = getWorld(physicsNode);
1194 if (world) {
1195 world->m_newPhysicsNodes.push_back(physicsNode);
1196 } else {
1197 worldManager.orphanNodes.push_back(physicsNode);
1198 }
1199}
1200
1202{
1203 for (auto world : worldManager.worlds) {
1204 world->m_newPhysicsNodes.removeAll(physicsNode);
1205 if (physicsNode->m_backendObject) {
1206 physicsNode->m_backendObject->isRemoved = true;
1207 physicsNode->m_backendObject = nullptr;
1208 }
1209 QMutexLocker locker(&world->m_removedPhysicsNodesMutex);
1210 world->m_removedPhysicsNodes.insert(physicsNode);
1211 }
1212 worldManager.orphanNodes.removeAll(physicsNode);
1213}
1214
1216{
1217 m_physx = new PhysXWorld;
1218 m_physx->createWorld();
1219
1220 worldManager.worlds.push_back(this);
1221 matchOrphanNodes();
1222}
1223
1225{
1226 m_workerThread.quit();
1227 m_workerThread.wait();
1228 for (auto body : m_physXBodies) {
1229 body->cleanup(m_physx);
1230 delete body;
1231 }
1232 m_physx->deleteWorld();
1233 delete m_physx;
1234 worldManager.worlds.removeAll(this);
1235}
1236
1238
1240{
1241 if (!m_running || m_physicsInitialized)
1242 return;
1243 initPhysics();
1244 emit simulateFrame(m_minTimestep, m_maxTimestep);
1245}
1246
1248{
1249 return m_gravity;
1250}
1251
1253{
1254 return m_running;
1255}
1256
1258{
1259 return m_forceDebugDraw;
1260}
1261
1263{
1264 return m_enableCCD;
1265}
1266
1268{
1269 return m_typicalLength;
1270}
1271
1273{
1274 return m_typicalSpeed;
1275}
1276
1278{
1279 return m_removedPhysicsNodes.contains(object);
1280}
1281
1283{
1284 if (m_gravity == gravity)
1285 return;
1286
1287 m_gravity = gravity;
1288 if (m_physx->scene) {
1289 m_physx->scene->setGravity(QPhysicsUtils::toPhysXType(m_gravity));
1290 }
1291 emit gravityChanged(m_gravity);
1292}
1293
1295{
1296 if (m_running == running)
1297 return;
1298
1299 m_running = running;
1300 if (m_running && !m_physicsInitialized)
1301 initPhysics();
1302 if (m_running)
1303 emit simulateFrame(m_minTimestep, m_maxTimestep);
1304 emit runningChanged(m_running);
1305}
1306
1307void QPhysicsWorld::setForceDebugDraw(bool forceDebugDraw)
1308{
1309 if (m_forceDebugDraw == forceDebugDraw)
1310 return;
1311
1312 m_forceDebugDraw = forceDebugDraw;
1313 if (!m_forceDebugDraw)
1314 disableDebugDraw();
1315 else
1316 updateDebugDraw();
1317 emit forceDebugDrawChanged(m_forceDebugDraw);
1318}
1319
1321{
1322 return m_viewport;
1323}
1324
1326{
1327 m_hasIndividualDebugDraw = true;
1328}
1329
1330void QPhysicsWorld::setViewport(QQuick3DNode *viewport)
1331{
1332 if (m_viewport == viewport)
1333 return;
1334
1335 m_viewport = viewport;
1336
1337 // TODO: test this
1338 for (auto material : m_debugMaterials)
1339 delete material;
1340 m_debugMaterials.clear();
1341
1342 for (auto &holder : m_collisionShapeDebugModels) {
1343 delete holder.model;
1344 }
1345 m_collisionShapeDebugModels.clear();
1346
1347 emit viewportChanged(m_viewport);
1348}
1349
1350void QPhysicsWorld::setMinimumTimestep(float minTimestep)
1351{
1352 if (qFuzzyCompare(m_minTimestep, minTimestep))
1353 return;
1354
1355 if (minTimestep > m_maxTimestep) {
1356 qWarning("Minimum timestep greater than maximum timestep, value clamped");
1357 minTimestep = qMin(minTimestep, m_maxTimestep);
1358 }
1359
1360 if (minTimestep < 0.f) {
1361 qWarning("Minimum timestep less than zero, value clamped");
1362 minTimestep = qMax(minTimestep, 0.f);
1363 }
1364
1365 if (qFuzzyCompare(m_minTimestep, minTimestep))
1366 return;
1367
1368 m_minTimestep = minTimestep;
1369 emit minimumTimestepChanged(m_minTimestep);
1370}
1371
1372void QPhysicsWorld::setMaximumTimestep(float maxTimestep)
1373{
1374 if (qFuzzyCompare(m_maxTimestep, maxTimestep))
1375 return;
1376
1377 if (maxTimestep < 0.f) {
1378 qWarning("Maximum timestep less than zero, value clamped");
1379 maxTimestep = qMax(maxTimestep, 0.f);
1380 }
1381
1382 if (qFuzzyCompare(m_maxTimestep, maxTimestep))
1383 return;
1384
1385 m_maxTimestep = maxTimestep;
1386 emit maximumTimestepChanged(maxTimestep);
1387}
1388
1389void QPhysicsWorld::updateDebugDraw()
1390{
1391 if (!(m_forceDebugDraw || m_hasIndividualDebugDraw)) {
1392 // Nothing to draw, trash all previous models (if any) and return
1393 if (!m_collisionShapeDebugModels.isEmpty()) {
1394 for (const auto& holder : std::as_const(m_collisionShapeDebugModels))
1395 delete holder.model;
1396 m_collisionShapeDebugModels.clear();
1397 }
1398 return;
1399 }
1400
1401 // Use scene node if no viewport has been specified
1402 auto sceneNode = m_viewport ? m_viewport : m_scene;
1403
1404 if (sceneNode == nullptr)
1405 return;
1406
1407 if (m_debugMaterials.isEmpty()) {
1408 // These colors match the indices of DebugDrawBodyType enum
1412 auto debugMaterial = new QQuick3DDefaultMaterial();
1413 debugMaterial->setLineWidth(3);
1414 debugMaterial->setParentItem(sceneNode);
1415 debugMaterial->setParent(sceneNode);
1416 debugMaterial->setDiffuseColor(color);
1417 debugMaterial->setLighting(QQuick3DDefaultMaterial::NoLighting);
1418 debugMaterial->setCullMode(QQuick3DMaterial::NoCulling);
1419 m_debugMaterials.push_back(debugMaterial);
1420 }
1421 }
1422
1423 m_hasIndividualDebugDraw = false;
1424
1425 // Store the collision shapes we have now so we can clear out the removed ones
1427 currentCollisionShapes.reserve(m_collisionShapeDebugModels.size());
1428
1429 for (QAbstractPhysXNode *node : m_physXBodies) {
1430 if (!node->debugGeometryCapability())
1431 continue;
1432
1433 const auto &collisionShapes = node->frontendNode->getCollisionShapesList();
1434 const int materialIdx = static_cast<int>(node->getDebugDrawBodyType());
1435 const int length = collisionShapes.length();
1436 if (node->shapes.length() < length)
1437 continue; // CharacterController has shapes, but not PhysX shapes
1438 for (int idx = 0; idx < length; idx++) {
1439 const auto collisionShape = collisionShapes[idx];
1440
1441 if (!m_forceDebugDraw && !collisionShape->enableDebugDraw())
1442 continue;
1443
1444 const auto physXShape = node->shapes[idx];
1445 DebugModelHolder &holder =
1446 m_collisionShapeDebugModels[std::make_pair(collisionShape, node)];
1447 auto &model = holder.model;
1448
1449 currentCollisionShapes.insert(std::make_pair(collisionShape, node));
1450
1451 m_hasIndividualDebugDraw =
1452 m_hasIndividualDebugDraw || collisionShape->enableDebugDraw();
1453
1454 auto localPose = physXShape->getLocalPose();
1455
1456 // Create/Update debug view infrastructure
1457 if (!model) {
1458 model = new QQuick3DModel();
1459 model->setParentItem(sceneNode);
1460 model->setParent(sceneNode);
1461 model->setCastsShadows(false);
1462 model->setReceivesShadows(false);
1463 model->setCastsReflections(false);
1464 }
1465
1466 { // update or set material
1467 auto material = m_debugMaterials[materialIdx];
1468 QQmlListReference materialsRef(model, "materials");
1469 if (materialsRef.count() == 0 || materialsRef.at(0) != material) {
1470 materialsRef.clear();
1471 materialsRef.append(material);
1472 }
1473 }
1474
1475 switch (physXShape->getGeometryType()) {
1476 case physx::PxGeometryType::eBOX: {
1477 physx::PxBoxGeometry boxGeometry;
1478 physXShape->getBoxGeometry(boxGeometry);
1479 const auto &halfExtentsOld = holder.halfExtents();
1480 const auto halfExtents = QPhysicsUtils::toQtType(boxGeometry.halfExtents);
1481 if (!qFuzzyCompare(halfExtentsOld, halfExtents)) {
1482 auto geom = QDebugDrawHelper::generateBoxGeometry(halfExtents);
1483 geom->setParent(model);
1484 model->setGeometry(geom);
1485 holder.setHalfExtents(halfExtents);
1486 }
1487
1488 }
1489 break;
1490
1491 case physx::PxGeometryType::eSPHERE: {
1492 physx::PxSphereGeometry sphereGeometry;
1493 physXShape->getSphereGeometry(sphereGeometry);
1494 const float radius = holder.radius();
1495 if (!qFuzzyCompare(sphereGeometry.radius, radius)) {
1496 auto geom = QDebugDrawHelper::generateSphereGeometry(sphereGeometry.radius);
1497 geom->setParent(model);
1498 model->setGeometry(geom);
1499 holder.setRadius(sphereGeometry.radius);
1500 }
1501 }
1502 break;
1503
1504 case physx::PxGeometryType::eCAPSULE: {
1505 physx::PxCapsuleGeometry capsuleGeometry;
1506 physXShape->getCapsuleGeometry(capsuleGeometry);
1507 const float radius = holder.radius();
1508 const float halfHeight = holder.halfHeight();
1509
1510 if (!qFuzzyCompare(capsuleGeometry.radius, radius)
1511 || !qFuzzyCompare(capsuleGeometry.halfHeight, halfHeight)) {
1513 capsuleGeometry.radius, capsuleGeometry.halfHeight);
1514 geom->setParent(model);
1515 model->setGeometry(geom);
1516 holder.setRadius(capsuleGeometry.radius);
1517 holder.setHalfHeight(capsuleGeometry.halfHeight);
1518 }
1519 }
1520 break;
1521
1522 case physx::PxGeometryType::ePLANE:{
1523 physx::PxPlaneGeometry planeGeometry;
1524 physXShape->getPlaneGeometry(planeGeometry);
1525 // Special rotation
1526 const QQuaternion rotation =
1528 localPose = physx::PxTransform(localPose.p, QPhysicsUtils::toPhysXType(rotation));
1529
1530 if (model->geometry() == nullptr) {
1532 geom->setParent(model);
1533 model->setGeometry(geom);
1534 }
1535 }
1536 break;
1537
1538 case physx::PxGeometryType::eHEIGHTFIELD: {
1539 physx::PxHeightFieldGeometry heightFieldGeometry;
1540 physXShape->getHeightFieldGeometry(heightFieldGeometry);
1541 const float heightScale = holder.heightScale();
1542 const float rowScale = holder.rowScale();
1543 const float columnScale = holder.columnScale();
1544
1545 if (!qFuzzyCompare(heightFieldGeometry.heightScale, heightScale)
1546 || !qFuzzyCompare(heightFieldGeometry.rowScale, rowScale)
1547 || !qFuzzyCompare(heightFieldGeometry.columnScale, columnScale)) {
1548
1550 heightFieldGeometry.heightField, heightFieldGeometry.heightScale,
1551 heightFieldGeometry.rowScale, heightFieldGeometry.columnScale);
1552 geom->setParent(model);
1553 model->setGeometry(geom);
1554 holder.setHeightScale(heightFieldGeometry.heightScale);
1555 holder.setRowScale(heightFieldGeometry.rowScale);
1556 holder.setColumnScale(heightFieldGeometry.columnScale);
1557 }
1558 }
1559 break;
1560
1561 case physx::PxGeometryType::eCONVEXMESH: {
1562 physx::PxConvexMeshGeometry convexMeshGeometry;
1563 physXShape->getConvexMeshGeometry(convexMeshGeometry);
1564 const auto rotation = convexMeshGeometry.scale.rotation * localPose.q;
1565 localPose = physx::PxTransform(localPose.p, rotation);
1566 model->setScale(QPhysicsUtils::toQtType(convexMeshGeometry.scale.scale));
1567
1568 if (model->geometry() == nullptr) {
1570 convexMeshGeometry.convexMesh);
1571 geom->setParent(model);
1572 model->setGeometry(geom);
1573 }
1574 }
1575 break;
1576
1577 case physx::PxGeometryType::eTRIANGLEMESH: {
1578 physx::PxTriangleMeshGeometry triangleMeshGeometry;
1579 physXShape->getTriangleMeshGeometry(triangleMeshGeometry);
1580 const auto rotation = triangleMeshGeometry.scale.rotation * localPose.q;
1581 localPose = physx::PxTransform(localPose.p, rotation);
1582 model->setScale(QPhysicsUtils::toQtType(triangleMeshGeometry.scale.scale));
1583
1584 if (model->geometry() == nullptr) {
1586 triangleMeshGeometry.triangleMesh);
1587 geom->setParent(model);
1588 model->setGeometry(geom);
1589 }
1590 }
1591 break;
1592
1593 case physx::PxGeometryType::eINVALID:
1594 case physx::PxGeometryType::eGEOMETRY_COUNT:
1595 // should not happen
1596 Q_UNREACHABLE();
1597 }
1598
1599 model->setVisible(true);
1600
1601 auto globalPose = node->getGlobalPose();
1602 auto finalPose = globalPose.transform(localPose);
1603
1604 model->setRotation(QPhysicsUtils::toQtType(finalPose.q));
1605 model->setPosition(QPhysicsUtils::toQtType(finalPose.p));
1606 }
1607 }
1608
1609 // Remove old collision shapes
1610 m_collisionShapeDebugModels.removeIf(
1612 DebugModelHolder>::iterator it) {
1613 if (!currentCollisionShapes.contains(it.key())) {
1614 auto holder = it.value();
1615 if (holder.model)
1616 delete holder.model;
1617 return true;
1618 }
1619 return false;
1620 });
1621}
1622
1623void QPhysicsWorld::disableDebugDraw()
1624{
1625 m_hasIndividualDebugDraw = false;
1626
1627 for (QAbstractPhysXNode *body : m_physXBodies) {
1628 const auto &collisionShapes = body->frontendNode->getCollisionShapesList();
1629 const int length = collisionShapes.length();
1630 for (int idx = 0; idx < length; idx++) {
1631 const auto collisionShape = collisionShapes[idx];
1632 if (collisionShape->enableDebugDraw()) {
1633 m_hasIndividualDebugDraw = true;
1634 return;
1635 }
1636 }
1637 }
1638}
1639
1641{
1642 if (m_enableCCD == enableCCD)
1643 return;
1644
1645 if (m_physicsInitialized) {
1646 qWarning()
1647 << "Warning: Changing 'enableCCD' after physics is initialized will have no effect";
1648 return;
1649 }
1650
1651 m_enableCCD = enableCCD;
1652 emit enableCCDChanged(m_enableCCD);
1653}
1654
1655void QPhysicsWorld::setTypicalLength(float typicalLength)
1656{
1657 if (qFuzzyCompare(typicalLength, m_typicalLength))
1658 return;
1659
1660 if (typicalLength <= 0.f) {
1661 qWarning() << "Warning: 'typicalLength' value less than zero, ignored";
1662 return;
1663 }
1664
1665 if (m_physicsInitialized) {
1666 qWarning() << "Warning: Changing 'typicalLength' after physics is initialized will have "
1667 "no effect";
1668 return;
1669 }
1670
1671 m_typicalLength = typicalLength;
1672
1674}
1675
1676void QPhysicsWorld::setTypicalSpeed(float typicalSpeed)
1677{
1678 if (qFuzzyCompare(typicalSpeed, m_typicalSpeed))
1679 return;
1680
1681 if (m_physicsInitialized) {
1682 qWarning() << "Warning: Changing 'typicalSpeed' after physics is initialized will have "
1683 "no effect";
1684 return;
1685 }
1686
1687 m_typicalSpeed = typicalSpeed;
1688
1690}
1691
1693{
1694 return m_defaultDensity;
1695}
1696
1698{
1699 return m_minTimestep;
1700}
1701
1703{
1704 return m_maxTimestep;
1705}
1706
1707void QPhysicsWorld::setDefaultDensity(float defaultDensity)
1708{
1709 if (qFuzzyCompare(m_defaultDensity, defaultDensity))
1710 return;
1711 m_defaultDensity = defaultDensity;
1712
1713 // Go through all dynamic rigid bodies and update the default density
1714 for (QAbstractPhysXNode *body : m_physXBodies)
1715 body->updateDefaultDensity(m_defaultDensity);
1716
1718}
1719
1720// Remove physics world items that no longer exist
1721
1722void QPhysicsWorld::cleanupRemovedNodes()
1723{
1724 m_physXBodies.removeIf([this](QAbstractPhysXNode *body) {
1725 return body->cleanupIfRemoved(m_physx);
1726 });
1727 // We don't need to lock the mutex here since the simulation
1728 // worker is waiting
1729 m_removedPhysicsNodes.clear();
1730}
1731
1732void QPhysicsWorld::initPhysics()
1733{
1734 Q_ASSERT(!m_physicsInitialized);
1735
1736 m_physx->createScene(m_typicalLength, m_typicalSpeed, m_gravity, m_enableCCD, this);
1737
1738 // Setup worker thread
1739 SimulationWorker *worker = new SimulationWorker(m_physx);
1740 worker->moveToThread(&m_workerThread);
1741 connect(&m_workerThread, &QThread::finished, worker, &QObject::deleteLater);
1743 connect(worker, &SimulationWorker::frameDone, this, &QPhysicsWorld::frameFinished);
1744 m_workerThread.start();
1745
1746 m_physicsInitialized = true;
1747}
1748
1749void QPhysicsWorld::frameFinished(float deltaTime)
1750{
1751 matchOrphanNodes();
1752 cleanupRemovedNodes();
1753 for (auto *node : std::as_const(m_newPhysicsNodes)) {
1754 auto *body = QPhysXFactory::createBackend(node);
1755 body->init(this, m_physx);
1756 m_physXBodies.push_back(body);
1757 }
1758 m_newPhysicsNodes.clear();
1759
1760 QHash<QQuick3DNode *, QMatrix4x4> transformCache;
1761
1762 // TODO: Use dirty flag/dirty list to avoid redoing things that didn't change
1763 for (auto *physXBody : std::as_const(m_physXBodies)) {
1764 physXBody->markDirtyShapes();
1765 physXBody->rebuildDirtyShapes(this, m_physx);
1766
1767 // Sync the physics world and the scene
1768 physXBody->sync(deltaTime, transformCache);
1769 }
1770
1771 updateDebugDraw();
1772
1773 if (m_running)
1774 emit simulateFrame(m_minTimestep, m_maxTimestep);
1775 emit frameDone(deltaTime * 1000);
1776}
1777
1779{
1781 if (!world->m_scene) {
1782 continue;
1783 }
1784
1785 QQuick3DNode *nodeCurr = node;
1786
1787 // Maybe pointless but check starting node
1788 if (nodeCurr == world->m_scene)
1789 return world;
1790
1791 while (nodeCurr->parentNode()) {
1792 nodeCurr = nodeCurr->parentNode();
1793 if (nodeCurr == world->m_scene)
1794 return world;
1795 }
1796 }
1797
1798 return nullptr;
1799}
1800
1801void QPhysicsWorld::matchOrphanNodes()
1802{
1803 // FIXME: does this need thread safety?
1804 if (worldManager.orphanNodes.isEmpty())
1805 return;
1806
1807 qsizetype numNodes = worldManager.orphanNodes.length();
1808 qsizetype idx = 0;
1809
1810 while (idx < numNodes) {
1811 auto node = worldManager.orphanNodes[idx];
1812 auto world = getWorld(node);
1813 if (world == this) {
1814 world->m_newPhysicsNodes.push_back(node);
1815 // swap-erase
1816 worldManager.orphanNodes.swapItemsAt(idx, numNodes - 1);
1817 worldManager.orphanNodes.pop_back();
1818 numNodes--;
1819 } else {
1820 idx++;
1821 }
1822 }
1823}
1824
1825void QPhysicsWorld::findPhysicsNodes()
1826{
1827 // This method finds the physics nodes inside the scene pointed to by the
1828 // scene property. This method is necessary to run whenever the scene
1829 // property is changed.
1830 if (m_scene == nullptr)
1831 return;
1832
1833 // Recursively go through all children and add all QAbstractPhysicsNode's
1834 QList<QQuick3DObject *> children = m_scene->childItems();
1835 while (!children.empty()) {
1836 auto child = children.takeFirst();
1837 if (auto converted = qobject_cast<QAbstractPhysicsNode *>(child); converted != nullptr) {
1838 // This should never happen but check anyway.
1839 if (converted->m_backendObject != nullptr) {
1840 qWarning() << "Warning: physics node already associated with a backend node.";
1841 continue;
1842 }
1843
1844 m_newPhysicsNodes.push_back(converted);
1845 worldManager.orphanNodes.removeAll(converted); // No longer orphan
1846 }
1847 children.append(child->childItems());
1848 }
1849}
1850
1851physx::PxPhysics *QPhysicsWorld::getPhysics()
1852{
1853 return s_physx.physics;
1854}
1855
1856physx::PxCooking *QPhysicsWorld::getCooking()
1857{
1858 return s_physx.cooking;
1859}
1860
1861physx::PxControllerManager *QPhysicsWorld::controllerManager()
1862{
1863 if (m_physx->scene && !m_physx->controllerManager) {
1864 m_physx->controllerManager = PxCreateControllerManager(*m_physx->scene);
1865 qCDebug(lcQuick3dPhysics) << "Created controller manager" << m_physx->controllerManager;
1866 }
1867 return m_physx->controllerManager;
1868}
1869
1871{
1872 return m_scene;
1873}
1874
1875void QPhysicsWorld::setScene(QQuick3DNode *newScene)
1876{
1877 if (m_scene == newScene)
1878 return;
1879
1880 m_scene = newScene;
1881
1882 // Delete all nodes since they are associated with the previous scene
1883 for (auto body : m_physXBodies) {
1885 }
1886
1887 // Check if scene is already used by another world
1888 bool sceneOK = true;
1889 for (QPhysicsWorld *world : worldManager.worlds) {
1890 if (world != this && world->scene() == newScene) {
1891 sceneOK = false;
1892 qWarning() << "Warning: scene already associated with physics world";
1893 }
1894 }
1895
1896 if (sceneOK)
1897 findPhysicsNodes();
1898 emit sceneChanged();
1899}
1900
1902
1903#include "qphysicsworld.moc"
void onShapeHit(const physx::PxControllerShapeHit &hit) override
void onControllerHit(const physx::PxControllersHit &) override
ControllerCallback(QPhysicsWorld *worldIn)
void onObstacleHit(const physx::PxControllerObstacleHit &) override
virtual bool useTriggerFlag()
virtual void sync(float deltaTime, QHash< QQuick3DNode *, QMatrix4x4 > &transformCache)=0
QAbstractPhysicsNode * frontendNode
QVector< physx::PxShape * > shapes
virtual void createMaterial(PhysXWorld *physX)
virtual void cleanup(PhysXWorld *)
physx::PxMaterial * material
QAbstractPhysXNode(QAbstractPhysicsNode *node)
bool shapesDirty() const
virtual physx::PxTransform getGlobalPose()
void setShapesDirty(bool dirty)
virtual DebugDrawBodyType getDebugDrawBodyType()
void createMaterialFromQtMaterial(PhysXWorld *physX, QPhysicsMaterial *qtMaterial)
virtual void init(QPhysicsWorld *world, PhysXWorld *physX)=0
virtual void updateDefaultDensity(float)
virtual void markDirtyShapes()
bool cleanupIfRemoved(PhysXWorld *physX)
virtual bool debugGeometryCapability()
static physx::PxMaterial * defaultMaterial
virtual void rebuildDirtyShapes(QPhysicsWorld *, PhysXWorld *)
virtual ~QAbstractPhysXNode()
const QVector< QAbstractCollisionShape * > & getCollisionShapesList() const
void updateFromPhysicsTransform(const physx::PxTransform &transform)
void registerContact(QAbstractPhysicsNode *body, const QVector< QVector3D > &positions, const QVector< QVector3D > &impulses, const QVector< QVector3D > &normals)
void shapeHit(QAbstractPhysicsNode *body, const QVector3D &position, const QVector3D &impulse, const QVector3D &normal)
QQueue< QPhysicsCommand * > & commandQueue()
QList< float > inertiaMatrix
QVector3D inertiaTensor
void setIsKinematic(bool isKinematic)
void updateDefaultDensity(float defaultDensity)
AxisLock angularAxisLock
AxisLock linearAxisLock
\inmodule QtCore
qint64 restart() noexcept
Restarts the timer and returns the number of milliseconds elapsed since the previous start.
void start() noexcept
Starts this timer.
qint64 nsecsElapsed() const noexcept
\inmodule QtCore
Definition qhash.h:818
qsizetype size() const noexcept
Returns the number of items in the hash.
Definition qhash.h:925
bool contains(const Key &key) const noexcept
Returns true if the hash contains an item with the key; otherwise returns false.
Definition qhash.h:991
qsizetype removeIf(Predicate pred)
Definition qhash.h:971
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:949
bool isEmpty() const noexcept
Returns true if the hash contains no items; otherwise returns false.
Definition qhash.h:926
Definition qlist.h:74
qsizetype size() const noexcept
Definition qlist.h:386
bool empty() const noexcept
Definition qlist.h:682
void push_back(parameter_type t)
Definition qlist.h:672
value_type takeFirst()
Definition qlist.h:549
qsizetype removeIf(Predicate pred)
Definition qlist.h:587
void reserve(qsizetype size)
Definition qlist.h:746
void append(parameter_type t)
Definition qlist.h:441
void clear()
Definition qlist.h:417
The QMatrix4x4 class represents a 4x4 transformation matrix in 3D space.
Definition qmatrix4x4.h:25
\inmodule QtCore
Definition qmutex.h:317
\inmodule QtCore
Definition qobject.h:90
const QObjectList & children() const
Returns a list of child objects.
Definition qobject.h:171
void moveToThread(QThread *thread)
Changes the thread affinity for this object and its children.
Definition qobject.cpp:1606
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2823
void setParent(QObject *parent)
Makes the object a child of parent.
Definition qobject.cpp:2142
void deleteLater()
\threadsafe
Definition qobject.cpp:2352
void markDirtyShapes() override
virtual void createActor(PhysXWorld *physX)
bool debugGeometryCapability() override
QPhysXActorBody(QAbstractPhysicsNode *frontEnd)
void buildShapes(PhysXWorld *physX)
void rebuildDirtyShapes(QPhysicsWorld *world, PhysXWorld *physX) override
physx::PxRigidActor * actor
void cleanup(PhysXWorld *physX) override
void init(QPhysicsWorld *world, PhysXWorld *physX) override
void sync(float deltaTime, QHash< QQuick3DNode *, QMatrix4x4 > &transformCache) override
physx::PxTransform getGlobalPose() override
QPhysXCharacterController(QCharacterController *frontEnd)
void init(QPhysicsWorld *world, PhysXWorld *physX) override
void cleanup(PhysXWorld *physX) override
void createMaterial(PhysXWorld *physX) override
void sync(float deltaTime, QHash< QQuick3DNode *, QMatrix4x4 > &transformCache) override
void updateDefaultDensity(float density) override
void rebuildDirtyShapes(QPhysicsWorld *world, PhysXWorld *physX) override
QPhysXDynamicBody(QDynamicRigidBody *frontEnd)
DebugDrawBodyType getDebugDrawBodyType() override
void sync(float deltaTime, QHash< QQuick3DNode *, QMatrix4x4 > &transformCache) override
static QAbstractPhysXNode * createBackend(QAbstractPhysicsNode *node)
QPhysXRigidBody(QAbstractPhysicsBody *frontEnd)
void createMaterial(PhysXWorld *physX) override
void createActor(PhysXWorld *physX) override
QPhysXStaticBody(QStaticRigidBody *frontEnd)
void sync(float deltaTime, QHash< QQuick3DNode *, QMatrix4x4 > &transformCache) override
DebugDrawBodyType getDebugDrawBodyType() override
DebugDrawBodyType getDebugDrawBodyType() override
QPhysXTriggerBody(QTriggerBody *frontEnd)
void sync(float deltaTime, QHash< QQuick3DNode *, QMatrix4x4 > &transformCache) override
bool useTriggerFlag() override
static constexpr float defaultDynamicFriction
static constexpr float defaultStaticFriction
static constexpr float defaultRestitution
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 setDefaultDensity(float defaultDensity)
void forceDebugDrawChanged(bool forceDebugDraw)
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 simulateFrame(float minTimestep, float maxTimestep)
void defaultDensityChanged(float defaultDensity)
void setGravity(QVector3D gravity)
static void registerNode(QAbstractPhysicsNode *physicsNode)
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.
Definition qquaternion.h:21
static QQuaternion fromEulerAngles(const QVector3D &eulerAngles)
static QQuaternion fromRotationMatrix(const QMatrix3x3 &rot3x3)
QQuaternion normalized() const
Returns the normalized unit form of this quaternion.
\inmodule QtCore
Definition qqueue.h:14
void enqueue(const T &t)
Adds value t to the tail of the queue.
Definition qqueue.h:18
QVector3D pivot
QVector3D sceneScale
Q_INVOKABLE QVector3D mapPositionFromScene(const QVector3D &scenePosition) const
\qmlmethod vector3d QtQuick3D::Node::mapPositionFromScene(vector3d scenePosition)
QVector3D scenePosition
void setPosition(const QVector3D &position)
QQuick3DNode * parentNode() const
QQuaternion rotation
QQuaternion sceneRotation
QVector3D position
QVector3D scale
Definition qset.h:18
void clear()
Definition qset.h:61
void reserve(qsizetype size)
Definition qset.h:222
bool contains(const T &value) const
Definition qset.h:71
iterator insert(const T &value)
Definition qset.h:155
void start(Priority=InheritPriority)
Definition qthread.cpp:923
bool wait(QDeadlineTimer deadline=QDeadlineTimer(QDeadlineTimer::Forever))
Definition qthread.cpp:950
static void usleep(unsigned long)
void finished(QPrivateSignal)
void quit()
Definition qthread.cpp:935
void registerCollision(QAbstractPhysicsNode *collision)
void deregisterCollision(QAbstractPhysicsNode *collision)
The QVector3D class represents a vector or vertex in 3D space.
Definition qvectornd.h:171
constexpr float y() const noexcept
Returns the y coordinate of this point.
Definition qvectornd.h:671
constexpr float x() const noexcept
Returns the x coordinate of this point.
Definition qvectornd.h:670
constexpr float z() const noexcept
Returns the z coordinate of this point.
Definition qvectornd.h:672
SimulationEventCallback(QPhysicsWorld *worldIn)
void onConstraintBreak(physx::PxConstraintInfo *, physx::PxU32) override
virtual ~SimulationEventCallback()=default
void onContact(const physx::PxContactPairHeader &pairHeader, const physx::PxContactPair *pairs, physx::PxU32 nbPairs) override
void onAdvance(const physx::PxRigidBody *const *, const physx::PxTransform *, const physx::PxU32) override
void onWake(physx::PxActor **, physx::PxU32) override
void onTrigger(physx::PxTriggerPair *pairs, physx::PxU32 count) override
void onSleep(physx::PxActor **, physx::PxU32) override
SimulationWorker(PhysXWorld *physx)
void frameDone(float deltaTime)
void simulateFrame(float minTimestep, float maxTimestep)
QSet< QString >::iterator it
constexpr QColor lightsalmon
Definition qcolor.h:389
constexpr QColor cyan
Definition qcolor.h:334
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)
Q_ALWAYS_INLINE QVector3D toQtType(const physx::PxVec3 &vec)
Combined button and popup list for selecting options.
struct QT_BEGIN_NAMESPACE::Holder holder
QMatrix3x3 Q_QUICK3DUTILS_EXPORT getUpper3x3(const QMatrix4x4 &m)
Definition qssgutils.cpp:51
void Q_QUICK3DUTILS_EXPORT normalize(QMatrix4x4 &m)
Definition qssgutils.cpp:57
QVector3D Q_QUICK3DUTILS_EXPORT getPosition(const QMatrix4x4 &m)
Definition qssgutils.cpp:97
std::pair< T1, T2 > QPair
static const QCssKnownValue positions[NumKnownPositionModes - 1]
static Q_CONSTINIT QBasicAtomicInt running
bool qFuzzyCompare(qfloat16 p1, qfloat16 p2) noexcept
Definition qfloat16.h:287
#define qWarning
Definition qlogging.h:162
#define qFatal
Definition qlogging.h:164
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
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
GLboolean GLboolean GLboolean b
GLsizei const GLfloat * v
[13]
GLboolean GLboolean GLboolean GLboolean a
[7]
GLenum GLuint GLenum GLsizei length
GLenum GLenum GLsizei count
GLbitfield flags
GLuint GLenum GLenum transform
GLenum GLsizei len
GLenum GLenum GLenum GLenum GLenum scale
StaticPhysXObjects s_physx
static physx::PxTransform getPhysXWorldTransform(const QQuick3DNode *node)
static physx::PxFilterFlags contactReportFilterShaderCCD(physx::PxFilterObjectAttributes, physx::PxFilterData, physx::PxFilterObjectAttributes, physx::PxFilterData, physx::PxPairFlags &pairFlags, const void *, physx::PxU32)
static physx::PxFilterFlags contactReportFilterShader(physx::PxFilterObjectAttributes, physx::PxFilterData, physx::PxFilterObjectAttributes, physx::PxFilterData, physx::PxPairFlags &pairFlags, const void *, physx::PxU32)
static QWorldManager worldManager
#define PHYSX_RELEASE(x)
DebugDrawBodyType
static bool fuzzyEquals(const physx::PxTransform &a, const physx::PxTransform &b)
static QMatrix4x4 calculateKinematicNodeTransform(QQuick3DNode *node, QHash< QQuick3DNode *, QMatrix4x4 > &transformCache)
static physx::PxTransform getPhysXLocalTransform(const QQuick3DNode *node)
static const QQuaternion kMinus90YawRotation
static void updatePhysXMaterial(const QPhysicsMaterial *qtMaterial, physx::PxMaterial *physXMaterial)
static physx::PxRigidDynamicLockFlags getLockFlags(QDynamicRigidBody *body)
static void processCommandQueue(QQueue< QPhysicsCommand * > &commandQueue, const QDynamicRigidBody &rigidBody, physx::PxRigidBody &body)
const qreal radiusScale
static qreal position(const QQuickItem *item, QQuickAnchors::Anchor anchorLine)
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define Q_OBJECT
#define slots
#define signals
#define emit
@ desc
ptrdiff_t qsizetype
Definition qtypes.h:70
unsigned int uint
Definition qtypes.h:29
double qreal
Definition qtypes.h:92
QSqlQueryModel * model
[16]
QSharedPointer< T > other(t)
[5]
rect sceneTransform().map(QPointF(0
view viewport() -> scroll(dx, dy, deviceRect)
QLayoutItem * child
[0]
QLatin1StringView world("world")
SimulationEventCallback * callback
physx::PxControllerManager * controllerManager
physx::PxScene * scene
void createScene(float typicalLength, float typicalSpeed, const QVector3D &gravity, bool enableCCD, QPhysicsWorld *physicsWorld)
static QMatrix4x4 calculateTransformMatrix(QVector3D position, QVector3D scale, QVector3D pivot, QQuaternion rotation)
QVector< QAbstractPhysicsNode * > orphanNodes
QVector< QPhysicsWorld * > worlds
physx::PxDefaultErrorCallback defaultErrorCallback
physx::PxDefaultAllocator defaultAllocatorCallback
physx::PxCooking * cooking
unsigned int foundationRefCount
physx::PxFoundation * foundation
physx::PxPvdTransport * transport
physx::PxDefaultCpuDispatcher * dispatcher
physx::PxPhysics * physics
IUIAutomationTreeWalker __RPC__deref_out_opt IUIAutomationElement ** parent