Revision control
++ // Copyright (C) 2023 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++ #include "qphysxactorbody_p.h"
++
++ #include "PxMaterial.h"
++ #include "PxPhysics.h"
++ #include "PxRigidDynamic.h"
++ #include "PxRigidActor.h"
++ #include "PxScene.h"
++
++ #include "physxnode/qphysxworld_p.h"
++ #include "qabstractphysicsbody_p.h"
++ #include "qheightfieldshape_p.h"
++ #include "qphysicsutils_p.h"
++ #include "qplaneshape_p.h"
++ #include "qstaticphysxobjects_p.h"
++
++ #include <QtGui/qquaternion.h>
++
++ #define PHYSX_RELEASE(x) \
++ if (x != nullptr) { \
++ x->release(); \
++ x = nullptr; \
++ }
++
++ QT_BEGIN_NAMESPACE
++
++ static physx::PxTransform getPhysXLocalTransform(const QQuick3DNode *node)
++ {
++ // Modify transforms to make the PhysX shapes match the QtQuick3D conventions
++ if (qobject_cast<const QPlaneShape *>(node) != nullptr) {
++ // Rotate the plane to make it match the built-in rectangle
++ const QQuaternion rotation = QPhysicsUtils::kMinus90YawRotation * node->rotation();
++ return physx::PxTransform(QPhysicsUtils::toPhysXType(node->position()),
++ QPhysicsUtils::toPhysXType(rotation));
++ } else if (auto *hf = qobject_cast<const QHeightFieldShape *>(node)) {
++ // Shift the height field so it's centered at the origin
++ return physx::PxTransform(QPhysicsUtils::toPhysXType(node->position() + hf->hfOffset()),
++ QPhysicsUtils::toPhysXType(node->rotation()));
++ }
++
++ const QQuaternion &rotation = node->rotation();
++ const QVector3D &localPosition = node->position();
++ const QVector3D &scale = node->sceneScale();
++ return physx::PxTransform(QPhysicsUtils::toPhysXType(localPosition * scale),
++ QPhysicsUtils::toPhysXType(rotation));
++ }
++
++ QPhysXActorBody::QPhysXActorBody(QAbstractPhysicsNode *frontEnd) : QAbstractPhysXNode(frontEnd) { }
++
++ void QPhysXActorBody::cleanup(QPhysXWorld *physX)
++ {
++ if (actor) {
++ physX->scene->removeActor(*actor);
++ PHYSX_RELEASE(actor);
++ }
++ QAbstractPhysXNode::cleanup(physX);
++ }
++
++ void QPhysXActorBody::init(QPhysicsWorld * /*world*/, QPhysXWorld *physX)
++ {
++ Q_ASSERT(!actor);
++
++ updateMaterial();
++ createActor(physX);
++
++ actor->userData = reinterpret_cast<void *>(frontendNode);
++ physX->scene->addActor(*actor);
++ setShapesDirty(true);
++ }
++
++ void QPhysXActorBody::sync(float /*deltaTime*/,
++ QHash<QQuick3DNode *, QMatrix4x4> & /*transformCache*/)
++ {
++ // The physicsMaterial property, and the properties of the material itself, can change while
++ // the simulation is running. Materials are shared between all nodes that need the same
++ // properties, so rather than modifying the material in place, the node is moved to the
++ // material with the new properties, and its shapes are pointed at it.
++ if (updateMaterial() && material) {
++ for (auto *shape : std::as_const(shapes))
++ shape->setMaterials(&material, 1);
++ }
++ }
++
++ void QPhysXActorBody::markDirtyShapes()
++ {
++ if (!frontendNode || !actor)
++ return;
++
++ // Go through the shapes and look for a change in pose (rotation, position)
++ // TODO: it is likely cheaper to connect a signal for changes on the position and rotation
++ // property and mark the node dirty then.
++ if (!shapesDirty()) {
++ const auto &collisionShapes = frontendNode->getCollisionShapesList();
++ const auto &physXShapes = shapes;
++
++ const int len = collisionShapes.size();
++ if (physXShapes.size() != len) {
++ // This should not really happen but check it anyway
++ setShapesDirty(true);
++ } else {
++ for (int i = 0; i < len; i++) {
++ auto poseNew = getPhysXLocalTransform(collisionShapes[i]);
++ auto poseOld = physXShapes[i]->getLocalPose();
++
++ if (!QPhysicsUtils::fuzzyEquals(poseNew, poseOld)) {
++ setShapesDirty(true);
++ break;
++ }
++ }
++ }
++ }
++ }
++
++ void QPhysXActorBody::rebuildDirtyShapes(QPhysicsWorld * /*world*/, QPhysXWorld *physX)
++ {
++ if (!shapesDirty())
++ return;
++ buildShapes(physX);
++ setShapesDirty(false);
++ }
++
++ void QPhysXActorBody::detachFrontend()
++ {
++ if (actor)
++ actor->userData = nullptr;
++ QAbstractPhysXNode::detachFrontend();
++ }
++
++ void QPhysXActorBody::createActor(QPhysXWorld * /*physX*/)
++ {
++ auto &s_physx = StaticPhysXObjects::getReference();
++ physx::PxTransform trf = QPhysicsUtils::toPhysXTransform(frontendNode->scenePosition(),
++ frontendNode->sceneRotation());
++ if (!trf.isSane()) {
++ qWarning() << "PhysicsNode: position/rotation is not finite, using identity instead.";
++ trf = physx::PxTransform(physx::PxIdentity);
++ }
++ auto *dynamicActor = s_physx.physics->createRigidDynamic(trf);
++
++ // A trigger has to be a dynamic actor, since PhysX only reports a trigger
++ // pair when one of the two is, but it is not simulated and nothing reads
++ // the velocity gravity would give it. Without gravity it stays where it is
++ // put and can fall asleep, which is what stops its pairs being tested:
++ // PhysX skips a trigger pair's overlap test only when both of its actors
++ // are asleep (see Sc::TriggerInteraction).
++ if (useTriggerFlag())
++ dynamicActor->setActorFlag(physx::PxActorFlag::eDISABLE_GRAVITY, true);
++
++ actor = dynamicActor;
++ }
++
++ bool QPhysXActorBody::debugGeometryCapability()
++ {
++ return true;
++ }
++
++ physx::PxTransform QPhysXActorBody::getGlobalPose()
++ {
++ return actor->getGlobalPose();
++ }
++
++ void QPhysXActorBody::buildShapes(QPhysXWorld * /*physX*/)
++ {
++ auto body = actor;
++ for (auto *shape : std::as_const(shapes)) {
++ body->detachShape(*shape);
++ PHYSX_RELEASE(shape);
++ }
++
++ // TODO: Only remove changed shapes?
++ shapes.clear();
++
++ for (const auto &collisionShape : frontendNode->getCollisionShapesList()) {
++ auto *geom = collisionShape->getPhysXGeometry();
++ if (!geom || !material)
++ continue;
++
++ auto &s_physx = StaticPhysXObjects::getReference();
++ // The shape is created exclusive to this actor: a shape that may be shared between
++ // actors cannot be modified once it is attached to one, and these shapes are modified
++ // while attached, for instance when their filter data changes.
++ auto physXShape = s_physx.physics->createShape(*geom, *material, true);
++ if (!physXShape) {
++ qWarning() << "QtQuick3DPhysics: could not create shape, invalid geometry.";
++ continue;
++ }
++
++ // Ties the PxShape back to the collision shape it was built for: a
++ // trigger pair is matched by those, which survive a rebuild replacing
++ // every PxShape of a node.
++ physXShape->userData = reinterpret_cast<void *>(collisionShape);
++
++ if (useTriggerFlag()) {
++ physXShape->setFlag(physx::PxShapeFlag::eSCENE_QUERY_SHAPE, false);
++ physXShape->setFlag(physx::PxShapeFlag::eSIMULATION_SHAPE, false);
++ physXShape->setFlag(physx::PxShapeFlag::eTRIGGER_SHAPE, true);
++ }
++
++ { // Setup filtering
++ physx::PxFilterData filterData;
++ filterData.word0 = frontendNode->filterGroup();
++ filterData.word1 = frontendNode->filterIgnoreGroups();
++ physXShape->setSimulationFilterData(filterData);
++ }
++
++ physx::PxTransform localPose = getPhysXLocalTransform(collisionShape);
++ if (!localPose.isSane()) {
++ qWarning() << "QtQuick3DPhysics: collision shape position/rotation is not finite, "
++ "using identity local pose instead.";
++ localPose = physx::PxTransform(physx::PxIdentity);
++ }
++ physXShape->setLocalPose(localPose);
++
++ // A shape PhysX will not take is not kept: left in 'shapes' it would make
++ // markDirtyShapes() find the body's shapes to be in order, so nothing would
++ // ever rebuild them, and the next rebuild would detach a shape that had never
++ // been attached.
++ if (!body->attachShape(*physXShape)) {
++ qWarning() << "QtQuick3DPhysics: could not attach collision shape to body, it will "
++ "not take part in the simulation.";
++ PHYSX_RELEASE(physXShape);
++ continue;
++ }
++
++ shapes.push_back(physXShape);
++ }
++
++ // Filters are always clean after building shapes
++ setFiltersDirty(false);
++ }
++
++ void QPhysXActorBody::updateFilters()
++ {
++ if (!filtersDirty())
++ return;
++
++ // Go through all shapes and set the filter group and mask.
++ // TODO: What about shared shapes on several actors?
++ for (auto &physXShape : shapes) {
++ physx::PxFilterData filterData;
++ filterData.word0 = frontendNode->filterGroup();
++ filterData.word1 = frontendNode->filterIgnoreGroups();
++ physXShape->setSimulationFilterData(filterData);
++ }
++
++ setFiltersDirty(false);
++ }
++
++ QT_END_NAMESPACE