GRK/cw 8/zadania 8.md
secret_dude a7bd7ecb75 master
2022-01-12 16:07:16 +01:00

7.0 KiB

Obsługa PhysX

PhysX jest silnikiem fizyki. Zadaniem silnika fizyki jest przeprowadzenie obliczeń związanych z symulacją zjawisk fizycznych jak dynamika brył sztywnych, płynów lub soft body dynamics. Wyręcza on twórcę gry w samodzielnej implementacji fizyki.

Innymi popularnymi silnikami fizyki są:

  • Havok
  • Bullet
  • Advanced Simulation Librar

My wykorzystamy PhysXa do symulacji dynamiki brył sztywnych. Dokumentację można znaleźć w poniższych linkach

https://gameworksdocs.nvidia.com/PhysX/4.1/documentation/physxguide/Manual/Index.html https://gameworksdocs.nvidia.com/PhysX/4.1/documentation/physxapi/files/index.html

Inicjalizacja PhysX

W tej części przejdziemy po istniejącym kodzie i omówimy jego działanie.

Zaczniemy od klasy Physics, która zawiera atrybut scene, (która zawiera naszą scenę, czyli obiekty aktorów, które mają ze sobą fizycznie reagować i ich własności.) oraz atrybut physics, który służy do tworzenia tych obiektów.

Jej konstruktor wygląda następująco:

Physics::Physics(float gravity)
{
    foundation = PxCreateFoundation(PX_PHYSICS_VERSION, allocator, errorCallback);
    physics = PxCreatePhysics(PX_PHYSICS_VERSION, *foundation, PxTolerancesScale(), true);

    PxSceneDesc sceneDesc(physics->getTolerancesScale());
    // określa siłę i kierunek grawitacji
    sceneDesc.gravity = PxVec3(0.0f, -gravity, 0.0f);
    // definicja dispatchera, który rozdziela zadania do wykonania, tutaj korzystamy z domyślnego dispatchera, któy będzie działał na 2 wątkach
    dispatcher = PxDefaultCpuDispatcherCreate(2);
    sceneDesc.cpuDispatcher = dispatcher;
    // definicja filtra, filtr decyduje między jakimi obiektami 
    sceneDesc.filterShader = PxDefaultSimulationFilterShader;
    scene = physics->createScene(sceneDesc);
}

Następnie mamy funkcję step, która wykonuje symulację, składa się ona z 2 kroków; najpierw wykonywana jest symulacja, następnie pobierane wyniki.

Położenie w scenie physXa musimy przenieść do naszej sceny macierze transformacji odpowiadające ich położeniu są pobierane w funkcji

void updateTransforms()
{
    // Definiujemy flagę jakie obiekty chcemy pobrać, w tym wypadku statyczne i dynamiczne.
    auto actorFlags = PxActorTypeFlag::eRIGID_DYNAMIC | PxActorTypeFlag::eRIGID_STATIC;
    PxU32 nbActors = pxScene.scene->getNbActors(actorFlags);
    if (nbActors)
    {
        //pobieramy obiekty
        std::vector<PxRigidActor*> actors(nbActors);
        pxScene.scene->getActors(actorFlags, (PxActor**)&actors[0], nbActors);
        for (auto actor : actors)
        {
            // Userdata jest atrybutem, któy przechowuje wskaźnik do obiektu zdefiniowanego przez nas, służy do powiądania aktora ze sceny z naszym obiektem. My będziemy tu przechowywać macierz transformacji
            if (!actor->userData) continue;
            glm::mat4 *modelMatrix = (glm::mat4*)actor->userData;

            // pobiera położenie aktora
            PxMat44 transform = actor->getGlobalPose();
            auto &c0 = transform.column0;
            auto &c1 = transform.column1;
            auto &c2 = transform.column2;
            auto &c3 = transform.column3;

            // ustawia wartości macierzy z userData
            *modelMatrix = glm::mat4(
                c0.x, c0.y, c0.z, c0.w,
                c1.x, c1.y, c1.z, c1.w,
                c2.x, c2.y, c2.z, c2.w,
                c3.x, c3.y, c3.z, c3.w);
        }
    }
}

Tworzenie aktora

Aktora tworzy się za pomocą metod klasy PxPhysics, jest ona dostępna u nas po pxScene.physics (pamiętaj, że jest to wskaźnik i trzeba używać strzałki zamiast kropki do wywołania metod).

Aby stworzyć aktora należy użyć metody createRigidStatic lub createRigidDynamic, które tworzą odpowiednio statycznego i dynamicznego aktora (o aktorze statycznym można pomyśleć jak o obiekcie z nieskończoną masą, na którego nie działają żadne siły). Funkcja przyjmuje argument transform, który jest początkową pozycją.

Następnie musimy określić kształt obiektu za pomocą za pomocą metody createShape ona z kolei przyjmuje 2 argumenty: geometrię i materiał. Geometria odpowiada za kształt obiektu (przykładowo PxPlaneGeometry() daje nam płaszczyznę a PxBoxGeometry(hx, hy, hz) daje prostopadłościan o wymiarach 2hx na 2hy na 2hz). Kształt dodaje się do aktora za pomocą metody attachShape. Dodany kształt należy usunąć używając jego metody release

Dodatkowo do aktora można dodać dodatkowe dane poprzez atrybut userData, który jest wskaźnikiem typu void*. Wykorzystywany jest by powiązać scenę silnika fizycznego z reprezentacją graficzną, która będzie na ekranie

Zadanie

Wykonaj zadania opisane w komentarzach pliku main_8_1 i main_8_2

Rejestrowanie zdarzeń

W tej części skupimy się na rejestrowaniu i obsłudze zderzeń pomiędzy obiektami. Jest to przydatna funkcjonalność przy tworzeniu gier lub ogólnie aplikacji wykorzystującej fizykę. Z ich pomocą można zaimplementować szereg mechanik, jak zadanie obrażeń w wyniku trafienia pociskiem/bronią białą, wywołanie animacji czy przytwierdzenie haka do ściany.

Implementacja wymaga najpierw zdefiniowania swojego filter shadera, w którym należy zdefiniować jak mają być obsłużone zdarzenia w scenie physx. W poniższej definicji definiujemy, że punkty zderzenia będą obsługiwane przez OnContact.

static PxFilterFlags simulationFilterShader(PxFilterObjectAttributes attributes0,
    PxFilterData filterData0, PxFilterObjectAttributes attributes1, PxFilterData filterData1,
    PxPairFlags& pairFlags, const void* constantBlock, PxU32 constantBlockSize)
{
    pairFlags =
        PxPairFlag::eCONTACT_DEFAULT | // default contact processing
        PxPairFlag::eNOTIFY_CONTACT_POINTS | // contact points will be available in onContact callback
        PxPairFlag::eNOTIFY_TOUCH_FOUND; // onContact callback will be called for this pair
        
    return physx::PxFilterFlag::eDEFAULT;
}

Samą obsługę zdarzeń definiujemy za pomocą wywołań zwrotnych - callbacków.

Wywołanie zwrotne można rozumieć jako odwrotność wywołania funkcji. Zwykle programista wykorzystuje biblioteki poprzez wywoływanie zawartych w niej funkcji. W tym przypadku jest odwrotnie: programista pisze funkcję i przekazuje ją bibliotece, która odpowiada za jej użycie w odpowiednim momencie.

Callback ustala się poprzez ustawienie odpowiedeniego atrybutu:

sceneDesc.simulationEventCallback = simulationEventCallback;

Klasa obiektu simulationEventCallback musi dziedziczyć po klasie PxSimulationEventCallback, która zawiera szereg metod będących różnymi callbackami odpowiadającymi za różne zdarzenia. Nas będzie interesować metoda onContact, która jest wywoływana dla każdego zetknięcia się dwóch obiektów w scenie.

Zadania

Wykonaj zadania opisane w main8_3.cpp.

Jak zrobisz raportowanie na konsoli zderzeń pomiędzy kulą a kostkami zmodyfikuj kod tak, żeby kostki, które zetkną się z kulą znikały