Biblioteka miesiąca
Box2D
Fizyczny świat w pudełku
Znudziło Ci się skryptowanie animacji, czy definiowanie pseudo-
realistycznych krzywych ruchu? Pragniesz dodać więcej realizmu do
swojej aplikacji, lecz nie czujesz się Newtonem? Nic straconego, skorzystaj
ze sprawdzonej już ścieżki: Box2D to biblioteka stworzona do symulacji
fizycznych światów 2D, dostępna dla każdego i to kompletnie za darmo!
brana, aby obejmowała ona wszystkie obiek-
Dowiesz się Powinieneś wiedzieć ty w scenie. Utworzenie obiektu poza ob-
" Jak w prosty i przystępny sposób rozpocząć " Jak programować w języku C++; szarem tej bryły skończy się na asercji (nie-
swą przygodę z Box2D; " Jak wykonywać podstawowe operacje na zorientowanych odsyłam do Słownika), co
" Do czego można zastosować Box2D; wektorach i macierzach w przestrzeni dwu- nie jest oczywiście błędem biblioteki. Nato-
" W jaki sposób definiować w Box2D fizyczne wymiarowej; miast wypadnięcie obiektu poza pre-definio-
ciała sztywne i jak modyfikować ich właści- " Podstawowe prawa dynamiki ruchu brył wany obszar świata spowoduje jego zamroże-
wości. sztywnych. nie (ang. freeze) i zakończenie jego symulacji.
Sytuację taką możemy wykryć rejestrując od-
powiedni obiekt słuchacza klasy pochodnej
nie przytłaczać treścią. Natomiast tematy z po- z b2BoundaryListener. Tworząc własną im-
granicza fizyki teoretycznej zostały zamknięte plementację takiego słuchacza, należy roz-
Poziom w Ramkach Rozważania niepoprawnego fizyka szerzyć metodę Violation(), która zosta-
trudności (choć może lepiej ich nie czytajcie). Zawarte tu nie wywołana w odpowiedzi na przekrocze-
wzory, równania i przemyślenia, to tylko mój nie zakresu świata przez dowolne ciało. Jed-
własny, uproszczony sposób postrzegania i tłu- nak podobnie jak inne funkcje zwrotne (ang.
maczenia fizycznego świata, więc nie traktuj- callbacks) w Box2D metoda ta nie może mo-
ie jestem fizykiem i (niestety) nie cie ich zbyt serio, a raczej jako wstęp, czy wpro- dyfikować bieżącego obiektu świata (na przy-
studiowałem tej pięknej dziedzi- wadzenie do wspaniałej i emocjonującej zaba- kład usuwać czy dodawać obiektów). Listing
Nny nauki, lecz podobnie jak zapew- wy z fizyką i Box em. Powodzenia. 2. prezentuje poprawny sposób usuwania
ne większość z Was, lubię wprawiać grę w obiektów, które wypadły poza obszar świata,
ruch, dawać życie jej obiektom. Czuję praw- Inicjalizacja symulacji fizycznej ponieważ w metodzie Violation() obiekty
dziwą satysfakcję gdy ruch obiektów jest na- Zanim utworzysz i wprawisz w ruch swo- nie mogą być bezpośrednio usuwane, są one
turalny, realistyczny i sprawia, że świat gry je pierwsze fizyczne obiekty niezbędne dodawane do kolejki, która może być prze-
przypomina świat rzeczywisty. Innymi sło- jest utworzenie obiektu świata b2World. twarzana po update cie symulacji.
wy cieszy mnie jak gra odzwierciedla pra- Obiekt ten definiuje podstawowe parame-
wa natury z którymi spotykamy się w co- try symulacji, takie jak wielkość świata, wek- Jednostki miary a stabilność symulacji
dziennym życiu. Biblioteka Box2D pozwa- tor grawitacji. Odpowiada on także za zarzą- Zarządzanie światem Box a wymaga przyję-
la każdemu programiście na przeniesienie dzanie pamięcią i tworzenie ciał sztywnych (i cia ujednoliconej skali i pewnego zrozumie-
tych marzeń na ekrany PC etów, Smartpho- nie tylko) będących pod jego kontrolą. Teo- nia miar przekazywanych i zwracanych z me-
ne ów i innych urządzeń, które można pro- retycznie możliwe jest utworzenie wielu in- tod biblioteki. Przy tworzeniu obiektów, de-
gramować w języku C++. Interfejs bibliote- stancji obiektów świata i przetwarzanie w finiowaniu ich własności czy wprawianiu ciał
ki jest zadziwiająco prosty i nie wymaga bie- ten sposób kilku symulacji jednocześnie, w w ruch posługujemy się wartościami, które
głej znajomości praw fizyki. Opracowanie praktyce korzystamy zazwyczaj z jednego. mają swoje odzwierciedlenie w realnym świe-
to opiera się na wersji 2.0.1 omawianej bi- Do konstruktora b2World podajemy maksy- cie i są w rzeczywistości wielkościami miano-
blioteki. malne rozmiary symulowanego świata w po- wanymi (np. prędkość można opisać w m/s,
Nawet ten sporych rozmiarów artykuł nie staci AABB (patrz Słownik), dwuwymiarowy km/h czy nawet węzłach).
jest w stanie przekazać wszystkich możliwo- wektor grawitacji (przyspieszenie grawitacyj- Standardowo arytmetyka w Box2D opar-
ści Box2D, więc starałem się raczej zwrócić ne), a także flagę informującą silnik, czy mo- ta jest na typie zmiennoprzecinkowym po-
uwagę na niektóre, ważniejsze, aspekty jego że wprowadzać on ciała w stan uśpienia (ang. jedynczej precyzji, float, choć może obsłu-
użytkowania. Bardziej wnikliwe spojrzenie na sleep), gdy nie są aktualnie w ruchu (patrz Li- giwać również operacje stałoprzecinkowe
szczegóły implementacji biblioteki znalazło sting 1.). Bryła otaczająca (AABB), definiu- (zwróć uwagę na definicję typu float32 zlo-
swe miejsce w ramkach Dla dociekliwych, by jąca wielkość świata, powinna być tak do- kalizowaną w pliku b2Settings.h na definicję
14 01/2010
Box2D
typu). Jednak niezależnie od wewnętrzne- patrz Rysunek 1). Podobnie w przypadku tości kroku, jest proporcjonalny do kwadra-
go formatu danych (float czy Fixed) skala obliczania pozycji (przemieszczenia ciała) tu kroku różnicowania i przestaje być akcep-
wartości używanych do interakcji i tworze- posłużono się formułą: towalny dla większych wartości kroku (patrz
nia świata ma decydujący wpływ na finalną Rysunek 1). Również bardzo małe wartości
jakość symulacji. Box2d został przygotowa- s ( t + "t ) = s( t ) + "t * v( t + "t ). dt nie są pożądane, gdyż mogą powodować
ny do obsługi jednostek w kilogramach, me- błędy zaokrągleń. Ponadto stabilność obli-
trach i sekundach. Oznacza to, iż jeśli przyj- Aproksymacja tych i innych wielkości fi- czeń może zakłócić duża zmienność tego
miemy, że masa naszego obiektu definiowa- zycznych jest wykonywana w dyskretnych kroku, dlatego zaleca się przetwarzanie sy-
na jest w kilogramach, to jego kształt powin- momentach czasu poprzez wywołanie : mulacji ze stałym krokiem dt <= 1/60 (czy-
niśmy opisywać, używając metrów jako jed- li częstotliwością 60 Hz). Oznaczało by to, iż
nostki odległości, jednocześnie prędkość te- b2World::Step( float32 dt, int32 iterations aplikacja powinna przetwarzać główną pętlę
go obiektu należy interpretować w m/s. ); z wydajnością 60 klatek na sekundę, co nie
Warty odnotowania jest też fakt, iż do sy- zawsze jest osiągalne. W związku z tym w
mulacji ruchomych ciał zalecane jest używa- gdzie wielkość kroku "t jest określona przez wielu przypadkach krok symulacji nie mo-
nie obiektów o wielkości od 0.1 do 10 me- argument dt. Metoda ta służy do przetwa- że być bezpośrednio powiązany z częstotli-
trów. Używanie tego systemu wielkości ma rzania symulacji i jej wywołanie umieszcza- wością odświeżania aplikacji.
kluczowy wpływ na dokładność obliczeń, my zazwyczaj w głównej pętli programowej,
dlatego też zazwyczaj powinniśmy stosować przekazując do niej czas (w sekundach), jaki Zarządzanie pamięcią
pewien współczynnik skalowania do mapo- upłyną od ostatniej ramki aplikacji. Niestety Ze względu na konieczność częstej aloka-
wania wielkości fizycznych na grafikę wi- błąd metody Euler a, nawet dla małych war- cji małych obiektów, niejednokrotnie o bar-
dzianą na ekranie monitora. Na pewno nie
można pokusić się na przyjmowanie piksela
Listing 1. Tworzenie obiektu świata i definiowanie parametrów symulacji
ekranowego jako jednostki wielkości obiek-
tów (1 piksel 1 metr), Box musiałby wte- bool allowSleep = true;
dy walczyć z obiektami rzędu wielkości Pe- b2Vec2 gravityAcc( 0.0f, -9.8f );
tronas Towers w Kuala Lumpur czy piramid b2AABB worldAABB;
w Gizie. Uwierzcie mi na słowo, że symulo- worldAABB.lowerBound.Set( -100.0f, 0.0f );
wanie takich kolosów nie jest najlepszą stro- worldAABB.upperBound.Set( 100.0f, 100.0f );
ną żadnego silnika fizyki, tylko wyobrazcie m_pWorld = new b2World( worldAABB, gravityAcc, allowSleep);
sobie siły, jakie wiążą się z ruchem takich return m_pWorld;
obiektów.
Listing 2. Implementacja słuchacza przekroczenia zakresu b2BoundaryListener
Integracja i jakość symulacji class CustomBoundaryListener : public b2BoundaryListener
Jak już wspomniałem, jednostki (skala) sy- {
mulacji mają olbrzymi wpływ na jej dokład- public:
ność, niestety nie tylko te wartości determi- void Violation( b2Body* body )
nują precyzję obliczeń. Box2D do rozwiąza- {
nia różniczkowych równań ruchu posługuje // Use body user data to store game logic object pointer.
się numeryczną metodą aproksymacji Eulera. PhysicEntity* entity = (PhysicEntity*)body->GetUserData();
Nie muszę dodawać, iż jest to metoda obar- m_destroyQueue.push_back( entity );
czona pewnym błędem. Równania typu: }
private:
dv = (F / m) dt std::vector< PhysicEntity* > m_destroyQueue;
};
gdzie dv i dt reprezentują nieskończenie ma-
łe zmiany prędkości i czasu, są przybliżane // Somewhere else during world initialization
w dyskretnych momentach czasu za pomocą m_pBoundsListener = new CustomBoundaryListener;
formuły (równania różnicowego): m_pWorld->SetBoundaryListener( m_pBoundsListener );
Listing 3. Usuwanie ciał z listy b2World
y ( x + "x ) = y( x ) + "x * y2 ( x ),
b2Body* bodyNode = m_pWorld->GetBodyList();
gdzie "x określa mierzalną (dyskretną) zmia- while( bodyNode )
nę argumentu funkcji wielkość kroku róż- {
nicowania, a y2 (x) to wartość jej pochodnej. // Store current body pointer
Rozpatrując tu równanie prędkości, mamy: b2Body* tempPtr = bodyNode;
// Get next body - iterates until NULL pointer received
v ( t + "t ) = v( t ) + "t * (F / m), bodyNode = tempPtr->GetNext();
// Destroy current body. Here you could also check
innymi słowy prędkość w danym momen- // if body belongs to some objects class or has some
cie czasu jest przybliżana na podstawie po- // specified id using body user data: tempPtr->GetUserData();
przedniej prędkości i różnicy czasowej po- m_pWorld->DestroyBody( tempPtr );
między tymi chwilami (pomnożonej de-fac- }
to przez jej pochodną, czyli przyspieszenie
01/2010 www.sdjournal.org 15
Biblioteka miesiąca
dzo krótkim czasie istnienia (np. dane ko- wet w każdej ramce symulacji, bez ciągłej ko- tem do puli. Fizyczna rezerwacja pamięci ze
lizji), Box2D wykorzystuje własny aloka- nieczności odwoływania się do pamięci sys- sterty (poprzez alokator systemowy) nastę-
tor (b2BlockAllocator) pamięci zoptyma- temowej (alokacji na stercie). Silnik rezer- puje tylko wtedy, gdy obsługiwana pula jest
lizowany do zarządzania małymi blokami wuje większe pule pamięci, które są rozdzie- już niewystarczająca, następuje wtedy aloka-
tzw. pool allocator (patrz Słownik). Pozwa- lane pomiędzy tworzone obiekty, a po ich cja kolejnej puli.
la on na szybką alokację wielu obiektów, na- zwolnieniu obszary te są zwracane z powro- Podejście to zdecydowanie zmniejsza
fragmentację pamięci, a także ogranicza
obciążenie szyny systemowej. Wymaga jed-
Dla dociekliwych: Własny alokator
nak dostosowania się do przyjętych w bi-
Architektura biblioteki została tak zaprojektowana, iż możliwe jest użycie własnego, niskopo-
bliotece konwencji. W związku z tym two-
ziomowego alokatora pamięci, skorzysta z niego również pool allocator. Implementacja Bo-
rzenie obiektów fizycznych (ciała sztyw-
x a nie wykorzystuje bezpośrednio alokacji systemowej, a używa jedynie funkcji b2Alloc i
b2Free, które ją przykrywają (standardowo wywołują one malloc i free z stdlib.h). Wystar- ne, wiązania, kształty) nigdy nie odbywa
czy zmienić implementację tych funkcji (plik b2Settings.cpp), aby korzystać z możliwości wła-
się poprzez alokację systemową opera-
snego alokatora. Ułatwia to również migrowanie biblioteki na inne platformy.
tor new czy funckję malloc, nigdy też nie
używaj delete czy free do usuwania tych
obiektów. Służą do tego metody fabryczne
klasy b2World:
Dla dociekliwych: Stack allocator
Podczas przetwarzania każdej ramki symulacji biblioteka intensywnie korzysta z pamięci
tymczasowej, w związku z tym wprowadzono dodatkowy typ alokatora. Do alokacji krótko-
b2Body* b2World::CreateBody(const
trwałych buforów pomocniczych silnik używa stack allocator a (b2StackAllocator). Choć
b2BodyDef*
jest to zaiste szczegół implementacji biblioteki, skrzętnie ukryty przed jej użytkownikiem,
bodyDef);
i nie implikuje on żadnych konsekwencji dla programisty, pozwolił on na utrzymanie dużej
b2Joint* b2World::CreateJoint(const
wydajności symulacji.
b2JointDef*
jointDef);
Dla dociekliwych: Debug owanie pamięci
albo metoda:
W wersji debug owej biblioteki (makrodefinicja _ DEBUG) alokator używany przez silnik
(b2BlockAllocator) ustawia zawartość zwolnionego bloku pamięci wartością 0xFD, co po-
b2Shape* b2Body::CreateShape(const
zwala niekiedy zlokalizować problemy z odwołaniami do usuniętych obiektów. Aczkolwiek
najczęściej pierwsze 4 bajty tego obszaru nie zawierają wartości 0xFD zapisany tam bę-
b2ShapeDef* def);
dzie adres następnego wolnego bloku w puli. Aby podejrzeć zawartość pamięci pod danym
wskaznikiem podczas sesji debug owej w środowisku Visual Studio, z menu głównego wy-
klasy b2Body, pozwalająca na tworzenie
bierz Debug->Windows->Memory->Memory1 (Alt+6), a następnie w pole adresu podaj adres
kształtów. Parametry inicjalizacji tych
zapisany pod wskaznikiem (możesz też podać nazwę zmiennej wskaznikowej, jeśli znajduje
obiektów są przekazywane w odpowied-
się ona w obszarze bieżącego break point a).
nich strukturach tak zwanych defini-
cjach (b2BodyDef, b2JointDef, b2ShapeDef).
Silnik nie utrzymuje referencji do tych
Rozważania niepoprawnego fizyka: Ciała statyczne
struktur, tylko kopiuje z nich dane, w
Główną cechą ciał statycznych jest brak masy czy też gęstości, a właściwie zerowa jej wartość.
związku z czym struktury te mogą być na-
Jak już wcześniej wspomniałem, w Box2D ciała te nie podlegają wpływowi grawitacji, co zda-
tychmiast usunięte lub użyte do utworze-
je się zgodne z prawem powszechnego ciążenia:
nia innych obiektów.
F = G * (m1 * m2) / (r * r).
Gdy zachodzi potrzeba usunięcia obiek-
tów ze świata Box a, używamy metod:
Stąd łatwo wnioskować, że siła przyciągania niezależnie od odległości ciał, jak i stałej grawita-
cyjnej (G) jest zawsze równa zeru (masa jednego z ciał jest zerowa).
Ciał tych nie może też wprawić w ruch żadna inna siła, co może nie jest już tak intuicyjne, bio- void b2World::DestroyBody(b2Body* body);
rąc pod uwagę szczególną teorię względności, która podaje, że ciała o zerowej masie poru- void b2World::DestroyJoint
szają się z prędkością równą prędkości światła (pomijając opory). Patrząc na formułę drugiej
(b2Joint* joint);
zasady dynamiki:
void b2Body::DestroyShape(b2Shape* shape);
a = F / m
Aczkolwiek należy pamiętać, że destruktor
należałoby raczej rozumieć, że obiekty statyczne mają nieskończoną masę, dlatego myśląc o
klasy b2World automatycznie usunie przy-
ciałach statycznych, lepiej pominąć względy matematyczno-estetyczne. Jednak zapewniam
należne do świata obiekty (b2Body, b2Joint),
Was, że próby wprowadzenia tych ciał w ruch za pomocą metod:
podobnie jak destruktor klasy b2Body usuwa
wszystkie przypięte do ciała kształty (b2Sha-
void b2Body::ApplyForce( const b2Vec2& force, const b2Vec2& point );
pe), wiązania i dane kolizji (b2Contact).
void b2Body::ApplyTorque( float32 torque );
void b2Body::ApplyImpulse( const b2Vec2& imp, const b2Vec2& point );
Box2d operuje na zwykłych wskaznikach
(brak obsługi smart pointers ani innego me-
skończą się fiaskiem. Podobnie ustawienie prędkości liniowej czy kątowej:
chanizmu zliczania referencji), dlatego po
usunięciu obiektu w gestii programisty le-
void b2Body::SetLinearVelocity( const b2Vec2& v );
ży usunięcie (wyzerowanie) wszystkich refe-
void b2Body::SetAngularVelocity( float32 omega );
rencji do tego obiektu i obiektów przynależ-
tych ciał nie spowoduje przemieszczenia czy obrotu tych ciał, choć może spowodować pewne
nych. Próba odwołania się do obiektu po jego
komplikacje przeczytaj Ramkę Uwaga: Prędkości ciał statycznych.
usunięciu spowoduje niezdefiniowane błędy,
a najprawdopodobniej crash aplikacji (zobacz
16 01/2010
Box2D
też Ramkę Dla dociekliwych: Debug owanie " statyczne, które nie wchodzą ze sobą w właśnie taką strukturę przekazuje się do
pamięci). W dużych projektach warto więc interakcje (nie kolidują ze sobą), nie pod- metody fabrycznej b2World::CreateBody.
pomyśleć o własnej implementacji uchwytów legają działaniu siły grawitacji i nie posia- Specyfikacja składowych struktury defi-
(ang. handle). dają masy; nicji została umieszczona w Tabelach 1 i
Choć b2World usuwa wszystkie przyna- " dynamiczne, mogą kolidować z innymi 2. W tabelach znajduje się też krótkie wy-
leżne ciała i węzły w momencie destruk- ciałami, zarówno statycznymi, jak i dy- jaśnienie wpływu poszczególnych składo-
cji, niekiedy zachodzi potrzeba usunięcia namicznymi, cechuje je przyspieszenie wych struktury na własności i symulację
pewnych obiektów jeszcze w trakcie trwa- grawitacyjne i masa większa od zera. tworzonych brył, zarówno statycznych,
nia symulacji. Na przykład jeśli obiekt wy- jak i dynamicznych.
padł poza obszar zdefiniowanego świa- Tyle teorii, przejdzmy do praktyki. W bi-
ta lub gdy przeładowujemy część mapy bliotece Box2D zanim utworzymy jakie- Więcej w papierowej wersji magazynu So-
gry. Usuwanie obiektów przynależnych do kolwiek ciało, potrzebujemy jego defini- ftware Developer s Journal.
świata fizycznego Box a może wydawać się cji, czyli zastawu cech opisujących fizycz-
na początku nieco kłopotliwe, gdyż wy- ne (i nie tylko) własności tworzonej bryły.
maga zrozumienia wewnętrznych struk- Dla przejrzystości i wygody programowa-
tur danych i operacji na nich wykonywa- nia, wszystkie cechy potrzebne do inicja-
nych. Wszystkie ciała fizyczne obsługiwa- lizacji i tworzenia brył sztywnych zosta-
ne przez bibliotekę są w rzeczywistości wę- ły upakowane w strukturze b2BodyDef i
złami swojej listy, dzięki czemu możliwa
jest iteracja po obiektach danego typu. Bi-
blioteka wykorzystuje te listy podczas prze-
twarzania ramki symulacji, z tego powodu
nie można ich modyfikować (dodawanie/
usuwanie obiektów) w funkcjach zwrot-
nych słuchaczy: b2DestructionListener,
b2BoundaryListener, b2ContactListener.
Funkcje zwrotne są wywoływane na etapie
przetwarzania symulacji, czyli podczas ite-
rowania po listach, a więc modyfikacja tych
list może spowodować destabilizację apli-
kacji. Listy te można również wykorzystać
do samodzielnego usuwania wielu obiek-
tów, należy tu jednak zachować ostrożność,
aby nie zakłócić spójności danych (czytaj:
nie wyłożyć aplikacji). Najlepiej posłużyć
się tu przykładem zamieszczonym w Li-
stingu 3.
Fizyka bryły sztywnej
Pojęcie bryły sztywnej (ang. rigid body) po-
chodzi oczywiście z teorii fizyki i definiuje
ciało, które nie zmienia swojej objętości ani
Rysunek 1. Interpretacja graficzna i błąd metody Eulera. yródło: Opracowanie własne
kształtu. Innymi słowy poszczególne punkty
tego ciała nigdy nie mogą się względem siebie
Tabela 1. Składowe definicji ciała i ich znaczenie dla ciał statycznych
przemieszczać. Box2D pozwala na symulację
właśnie takiego typu obiektów fizycznych. W Składowa Interpretacja dla ciał statycz- Wartość domyślna
nych
bibliotece można wyróżnić dwa rodzaje ciał
massData.mass 0.0f
sztywnych:
jeśli równa zero, wyznacza
ciało statyczne
massData.I 0.0f
nie dotyczy
Listing 4. Struktura definiująca rozkład
masy ciała
massData.center b2Vec(0.0f, 0.0f)
nie dotyczy
userData NULL
dane użytkownika
struct b2MassData
position b2Vec(0.0f, 0.0f)
pozycja w świecie
{
angle 0.0f
// Body mass [kg]
kąt obrotu (w radianach)
float32 mass;
linearDamping 0.0f
nie dotyczy
// Centroid position relative to
angularDamping 0.0f
nie dotyczy
body origin
allowSleep true
pozwalaj na usypianie
b2Vec2 center;
isSleeping false
// Rotational inertia [kg-m^2] uśpiony po utworzeniu
float32 I;
fixedRotation false
nie dotyczy
};
isBullet false
nie dotyczy
01/2010 www.sdjournal.org 17
Wyszukiwarka
Podobne podstrony:
LICENSE Box2Dwięcej podobnych podstron