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