Fizyka 2d z biblioteką Box2d
Box2d jest biblioteką napisaną w C++, która pozwala tworzyć bardzo realistyczne symulacje dwuwymiarowego świata.
Jej zaletami są szybkość i możliwość używania bez (wybitnych) zdolności w zakresie fizyki :P
Posiada także porty na inne języki (w tym Java, JavaScript i Flash)
Z użyciem Box2d zostało napisanych wiele gier, w tym bardzo popularne Angry Birds! (Kiedy nauczysz się co nieco o box'ie zrozumiesz, że wcale się przy tym tak bardzo nie napracowali. Wszystko dzięki Box2d!)
Bibliotekę można pobrać stąd:
https://code.google.com/p/box2d/downloads/detail?name=Box2D_v2.3.0.7z&can=2&q=(wersja 2.3.0 używana w tym tutorialu)
Nowsze wersje biblioteki mogą być hostingowane na innych serwerach.
Kilka podstawowych definicji
Ciało sztywne (ang.
rigid body) - ciało, które nie może zmienić swojego kształtu. Wszystkie ciała symulowane przez Box2d są ciałami sztywnymi.
Wiązanie (ang.
constraint, joint) - działanie, które w pewnym stopniu ogranicza ruch ciała
Ciało statyczne - ciało które nie posiada masy (lub posiada nieskończoną masę). Nie działają na nie żadne siły (włącznie z grawitacją). Koliduje z ciałami dynamicznymi. Nie może samo zmienić swojej pozycji.
Ciało kinematyczne - ciało które nie posiada masy (lub posiada nieskończoną masę). Nie działają na nie żadne siły (włącznie z grawitacją). Koliduje z ciałami dynamicznymi. Po nadaniu mu prędkości nie zmienia jej.
Ciało dynamiczne - "normalne" ciało. Posiada masę, działają na nie wszystkie siły, koliduje z wszystkimi innymi ciałami
Twoja pierwsza symulacja!
Najprostszy przykład symulacji Box2d.
Program symuluje spadanie klocka na wiszącą w powietrzu platformę.
Omówienia w komentarzach.
#include <Box2d/Box2d.h>
int main()
{
b2World world( b2Vec2( 0.0f, - 10.0f ) );
b2BodyDef groundDef;
groundDef.position.Set( 0.0f, - 10.0f );
b2Body * ground = world.CreateBody( & groundDef );
b2PolygonShape groundShape;
groundShape.SetAsBox( 100.0f / 2, 20.0f / 2 );
ground->CreateFixture( & groundShape, 1.0f );
b2BodyDef boxDef;
boxDef.type = b2_dynamicBody;
boxDef.position.Set( 0.0f, 4.0f );
b2Body * box = world.CreateBody( & boxDef );
b2PolygonShape boxShape;
boxShape.SetAsBox( 2.0f / 2, 2.0f / 2 );
b2FixtureDef boxFixDef;
boxFixDef.shape = & boxShape;
boxFixDef.density = 1.0f;
boxFixDef.friction = 0.3f;
box->CreateFixture( & boxFixDef );
float time = 1.0f;
int steps = 100.0f;
float timeStep = time / steps;
int velocityIt = 8, positionIt = 3;
for( int i = 0; i < steps; i++ )
{
world.Step( timeStep, velocityIt, positionIt );
b2Vec2 pos = box->GetPosition();
printf( "Krok %i : %4.2f, %4.2f", i, pos.x, pos.y );
}
}
Wynik powienien być mniej więcej taki:
Krok 0 : 0.00, 4.00
Krok 1 : 0.00, 3.99
Krok 2 : 0.00, 3.98
...
Krok 97 : 0.00, 1.25
Krok 98 : 0.00, 1.13
Krok 99 : 0.00, 1.01
Omówienia wymaga tylko ta linijka:
groundShape.SetAsBox( 100.0f / 2, 20.0f / 2 );
Otóż, metoda
SetAsBox() przyjmuje
połowy długości boków prostokąta. Należy to zapamiętać.
Jak działa Box2d?
Symulacja Box2d jest reprezentowana przez
świat - klasę
b2World.
Do każdego świata mogą być dodawane
ciała - klasa
b2Body (oraz wiązania, ale o tym później)
Każde ciało składa się z tzw.
fikstur - klasa
b2FixtureFikstury stanowią różne fragmenty jednego ciała. Moga mieć przypisane tarcie, gęstość i opór.
Poza tym każda fikstura ma swój kształt.
Fikstury w jednym ciele
nie mogą się względem siebie przemieszczać.
Oznacza to, że fikstury przywiązane do jednego ciała poruszają się razem.
Wszystkie klasy Box2d mają przyrostek
b2.
Box2d jest oparta na koncepcie tzw.
fabryki.
Oznacza to, że schemat tworzenia dzieci będzie taki:
Wlasciciel wl;
KlasaDef cosDef;
Klasa * cos = wl.addKlasa( cosDef );
wl.destroyKlasa( cos );
Symulacja Box2d nie jest "ciągła", lecz przybliżana w dyskretnych momentach za pomocą funkcji
b2World::Step().
Aktualizuje ona symulację o podany czas (przyrost czasu od poprzedniego wywołania)
Zatem ogólny zarys programu korzystającego z Box2d będzie taki:
b2World world( );
while( )
{
float deltaTime = ;
world.Step( deltaTime, );
}
Z przyczyn technicznych, Box2d operuje na metrach, nie pikselach. Tak czy inaczej, wszystkie wymiary muszą znajdować się między -10 a 10. Dlatego też wszystkie wartości wektorowe lub wyrażające prędkość (liniową), przesunięcie czy pozycje muszą być skalowane, jeśli za jednostki używasz pikseli. Dla gier jako współczynnik skalowania stosuje się zazwyczaj 0.02, ale jeśli symulujesz bardzo duże lub bardzo małe obiekty pamiętaj, żeby dobrze go dostosować. |
Box2d jako jednostki obrotu (kątu) i predkości kątowej używa radianów i radianów na sekundę. Jeśli chcesz używać stopni używaj tych makr:
#define DGtoRD(deg) (b2_pi * (deg) / 180.0f) #define RDtoDG(rad) ((rad) * (180.0f / b2_pi))
|
Klasy pomocnicze
b2Vec2
Implementacja wektora 2D.
Posiada pola
x i
y wyrażające przesunięcie na osiach X i Y
b2Vec2 v( 0.483, 5.35474 );
v.x += 0.2;
v.y *= 0.05;
v.Set( 0.2, 0.4 );
float l = v.Length();
b2Vec2 other = v;
v += other;
v *= 0.34;
b2AABB
Reprezentuje prostokąt o bokach prostopadłych do osi X i Y.
b2AABB aabb;
aabb.lowerBound = lt;
aabb.upperBound = rd;
b2Vec2 extent = aabb.GetExtent();
b2Vec2 center = aabb.GetCenter();
Kształty, czyli b2Shape
Kształty w Box2d reprezentuje klasa
b2Shape i jej dzieci.
Mamy do dyspozycji:
Można łatwo obliczyć AABB danego kształtu (nie dotyczy
b2ChainShape)
b2PolygonShape polygon;
b2AABB aabb;
polygon.ComputeAABB( & aabb, b2Transform(), 0 );
b2CircleShape
Reprezentuje koło (wypełnione w środku). Posiada promień
m_radius i pozycję środka
m_p.
b2CircleShape c;
c.m_radius = 0.3;
c.m_p.Set( 0.56, 0.576 );
b2PolygonShape
Reprezentuje wielokąt (wypełniony w środku). Tworzy się go z pozycji poszczególnych wierzchołków.
Nie może mieć ich więcej niż
b2_maxPolygonVerticies!
b2PolygonShape polygon;
polygon.SetAsBox( 0.35 / 2, 0.26 / 2 );
const int vxNum = 6;
b2Vec2 vx[ vxNum ] = { };
polygon.Set( vx, vxNum );
b2Vec2 vx2 = polygon.GetVertex( 2 );
polygon.GetVertexCount();
b2EdgeShape
Stanowi krawędź, linię między dwoma punktami. Nie ma grubości ani masy.
Krawędzie nie kolidują ze sobą. Może występować jedynie jako ciało statyczne.
Definiuje się ją za pomocą dwóch punktów, które ma łączyć.
b2EdgeShape edge;
edge.Set( vertex1, vertex2 );
edge.m_vertex1; edge.m_vertex2;
b2ChainShape
Łańcuch z krawędzi. Właściwości takie same jak krawędź.
const int vxNum = 9;
b2Vec2 vx[ vxNum ] = { };
b2ChainShape c1, c2;
c1.CreateChain( vx, vxNum );
c2.CreateLoop( vx, vxNum );
Iteracja po osobnych krawędziach w łańcuchu:
b2ChainShape ch;
for( int i = 0; i < ch.GetChildCount(); i++ )
{
b2EdgeShape ed;
ch.GetChildEdge( & ed, i );
}
Fikstury - b2Fixture
Fikstury, jak było mówione wcześniej stanowią różne fragmenty jednego ciała.
Często ciało ma tylko jedną fiksturę, ale mnogość fikstur jest przydatna:
Fikstury w jednym ciele nie kolidują ze sobąFikstura może mieć przypisane:
b2PolygonShape shape;
b2Body * body;
b2FixtureDef fixDef;
fixDef.shape = & shape;
fixDef.density = 1.0f;
fixDef.friction = 0.3f;
fixDef.restitution = 0.1f;
b2Fixture * fix1 = body->CreateFixture( & fixDef );
b2Fixture * fix2 = body->CreateFixture( & shape, 1.0 );
Z jednego kształtu lub jednej definicji fikstury możemy stworzyć wiele fikstur należących do różnych ciał.
Ponadto, podczas tworzenia fikstury kształt jest klonowany i alokowany, więc nie trzeba martwić się o zakresy widoczności.
Już stworzona
b2Fixture oferuje nam wiele możliwości:
fix->GetShape();
fix->GetBody();
fix->GetAABB( 0 );
fix->GetDensity();
fix->SetDensity( 0.67 );
Raz stworzona fikstura nie może zmieniać swojego kształtu! |
Filtrowanie kolizji
Jeśli nie chcemy, by niektóre fikstury ze soba kolidowały, możemy uzyć
filtrowania kolizji.
Każda fikstura może przynależeć do jednej lub kilku kategorii (maksymalnie 16).
Jeśli mamy ustawioną kategorię, możemy ustawić, czy dana fikstura ma kolidować z fiksturami z danej grupy, czy nie.
Jesli na przykład chcemy, żeby A nie kolidowało z B, ale kolidowało z C; natomiast B powinno kolidować z C.
#define CATEGORY(num) (1 << (num))
ADef.filter.categoryBits = CATEGORY( 0 );
ADef.filter.maskBits = CATEGORY( 2 );
BDef.filter.categoryBits = CATEGORY( 1 );
BDef.filter.maskBits = CATEGORY( 2 );
CDef.filter.categoryBits = CATEGORY( 2 );
CDef.filter.maskBits = CATEGORY( 1 ) | CATEGORY( 0 );
Domyślne wartość
categoryBits to 0x0000, a
maskBits 0xFFFF. Oznacza to, że domyślnie fikstura nie ma przypisanej żednej kategorii, ale może kolidować z wszystkimi kategoriami.
Ciała - b2Body
Ciało to kilka fikstur połączonych razem.
Definiują je następujące własności:
Ciało tworzymy za pomocą metody
CreateBody() klasy
b2World:
b2BodyDef bodyDef;
bodyDef.type = b2_dynamicBody;
bodyDef.position.Set( 0.0f, 3.5f );
bodyDef.angle = 0.5f;
bodyDef.damping = 0.04f;
bodyDef.gravityScale = 0.0f;
bodyDef.fixedRotation = true;
bodyDef.bullet = false;
b2Body * body = world.CreateBody( & bodyDef );
world.DestroyBody( body );
Działania na ciałach:
body->GetPosition();
body->GetAngle();
body->GetLocalCenter();
body->GetGlobalCenter();
body->GetMass();
body->GetLinearVelocity();
body->GetAngularVelocity();
body->GetType();
body->GetWorld();
body->GetWorldPoint( b2Vec2( 0.0f, 8.7f ) );
body->GetLocalPoint( b2Vec2( 3.5f, 5.6f ) );
Siły vs impulsy vs prędkości
Do każdego ciała możemy zaaplikować liniową albo kątową siłę (ang.
force), implus (ang.
impluse) lub prędkość (ang.
velocity).
Aplikując
prędkość ustawiamy bezwzględną szybkość, z jaką się porusza/obraca. Można to porównać do biegnącego chłopca, który nagle zderzył się ze ścianą. Ta sciana "zmieniła" jego prędkość do 0.
Aplikując
impuls przez chwilę działamy z dużą siłą na ciało. Na przykładzie chłopca: chłopiec biegnie i nagle ktoś mocno popchnął. Chłopiec zwiększa swoją prędkość, ale po chwili dodatkowe przyspieszenie znika, i chłopiec biegnie dalej.
Aplikując
siły przez cały krok symulacji działamy na ciało. Na przykładzie chłopca: chłopiec biegnie, a ktoś za nim ciągnie przywiązaną do niego linę.
Jeśli chłopiec początkowo biegł z siłą F
1, a ktoś zaczął go ciągnąć do tyłu z siłą F
2, to wynikowa siła poruszania się chłopca będzie wynosiła
F1 - F2.
Dodatkowo aplikowanie siły jest zależne od kroku czasowego. Im większy krok czasowy, tym jedna siła będzie miała różny wpływ na ciało.
body->SetLinearVelocity( b2Vec2( 0.0f, 2.4f ) );
body->SetAngularVelocity( 3.5f );
body->ApplyLinearImpulse( force, point );
body->ApplyAngularImpulse( 3.5f );
body->ApplyForce( force, point );
body->ApplyForceToCenter( b2Vec2( 0.5f, 3.45f ) );
body->ApplyTorque( 34.f );
Świat - b2World
Klasa świata jest klasą niezbędną do stworzenia symulacji. Świat może zawierać ciała i wiązania.
Symulowanie
Świat symulujemy za pomocą metody
Step().
Dokładność i szybkość symulacji wyznaczają następujące czynniki:
float deltaTime;
int positionIt, velocityIt;
world.Step( deltaTime, velocityIt, positionIt );
Iteracja po elementach świata
Iteracja po wszystkich ciałach:
for( b2Body * body = world.GetBodyList(); b; b = b->GetNext() )
{
}
Nigdy nie usuwaj ciała podczas iterowania! Sprawi to, że wywołanie metody GetNext() na tym ciele spowoduje crash programu! Ciało jest alokowane i dealokowane w metodach CreateBody() i DestroyBody(). Po zniszczeniu ciała wskaźnik będzie wskazywał na nieistniejący (już) obiekt. Zamiast tego zapisz wskaźnik na następne ciało:
b2Body * next = NULL; for( b2Body * body = world.GetBodyList(); b; b = next ) { next = body->GetNext(); }
|
Iteracja po wszystkich kolizjach występujących w tym kroku:
for( b2Contact * c = world.GetContactList(); c; c = c->GetNext() )
{
b2Fixture * fixtureA = c->GetFixtureA();
b2Fixture * fixtureB = c->GetFixtureB();
}
AABB Query i RayCast
Te metody szukają wszystkich ciał spełniających pewien warunek.
AABB Query (ang.
pobieranie po AABB) szuka ciał które w całości bądź częściowo leżą w podanym regionie.
RayCast (ang.
rzutowanie przez promień) szuka ciał, które leżą na odcinku między dwoma punktami
Pierwsza metoda:
class MyAABBQueryCallback
: public b2QueryCallback
{
public:
bool ReportFixture( b2Fixture * fixture )
{
return true;
return false;
}
};
MyAABBQueryCallback cb;
b2AABB aabb;
aabb.lowerBound.Set( - 1.0f, - 1.0f );
aabb.upperBound.Set( 1.0f, 1.0f );
myWorld->Query( & cb, aabb );
RayCast:
class MyRayCastCallback
: public b2RayCastCallback
{
public:
float ReportFixture( b2Fixture * fixture, const b2Vec2 & point, const b2Vec2 & normal, float32 fraction )
{
return 1;
return 0;
return fraction;
}
};
MyRayCastCallback cb;
b2Vec2 point1( - 1.0f, 0.0f );
b2Vec2 point2( 3.0f, 1.0f );
world.RayCast( & cb, point1, point2 );
Listenery, czyli "coś się stało"
Listenery czyli nasłuchiwacze wywołują określoną metodę, gdy coś się stanie.
Mamy dwa rodzaje listener'ów:
Destruction listener:
struct MyDestructionListener
: b2DestructionListener
{
void SayGoodbye( b2Fixture * fixture )
{
};
vois SayGoodbye( b2Joint * joint )
{
}
};
MyDestructionListener l;
world.SetDestructionListener( & l );
Contact listener:
struct MyContactListener
: b2ContactListener
{
void BeginContact( b2Contact * c )
{
b2Fixture * fixtureA = c->GetFixtureA();
b2Fixture * fixtureB = c->GetFixtureB();
}
void EndContact( b2Contact * c )
{
b2Fixture * fixtureA = c->GetFixtureA();
b2Fixture * fixtureB = c->GetFixtureB();
}
};
MyContactListener l;
world.SetContactListener( & l );
Nigdy nie usuwaj żadnego ciała, wiązania ani fikstury podczas callbacków BeginContact() i EndContact()! Próba zrobienia tego spowoduje błąd podobny do tego:
Assertion failed: line: xxx expression: IS_LOCKED(world) Zamiast tego zapamietaj fiksturę do zniszczenia:
std::queue < b2Body *> forDestroy;
struct MyContactListener : b2ContactListener { void BeginContact( b2Contact * c ) { if( haveToDestroyBody ) { forDestroy.push( body ); } } };
while( !forDestroy.empty() ) { world.DestroyBody( forDestroy.front() ); forDestroy.pop(); }
|
Wiązania - b2Joint
Nie, nie chodzi o takie dżointy :)
W Box2d
wiązania (
jointy) (jak już było wcześniej powiedziane) w pewnym stopniu ograniczają ruch ciała.
Każde wiązanie musi występować pomiędzy dwoma ciałami, z czego co najmniej jedno z nich musi być dynamiczne.
Każdy joint jest potomkiem klasy abstrakcyjnej
b2Joint i ma parametry:
Tworzymy je w następujący sposób:
b2XXXJointDef jointDef;
jointDef.Initialize( );
jointDef.collideConnected = true;
b2XXXJoint * joint =( b2XXXJoint * ) world.CreateJoint( & jointDef );
world.DestroyJoint( joint );
Każdy rodzaj wiązania odziedzicza po klasie
b2Joint następujące metody:
joint->GetBodyA();
joint->GetBodyB();
joint->GetAnchorA();
joint->GetAnchorB();
joint->GetCollideConnected();
Niektóre wiązania udostępniają
napęd (ang.
motor). Oznacza to, że jeśli wiązanie ma coś wspólnego z obracaniem się, będzie się obracało ze stałą prędkością
jointDef.motorEnabled = true;
jointDef.maxMotorTorque = 20;
jointDef.motorSpeed = 36;
Motor obsługują wiązania revolute, gear i wheel.
distance joint - utrzymanie dystansu
Distance joint utrzymuje stały dystans między dwoma punktami dwóch ciał.
Ten joint może być "twardy" - wtedy nie będzie pozwalał na zmianę dystansu niezlażnie od sił działających na ciała, albo "miękki" - będzie działał jak sprężyna
dążąca do utrzymania stałego dystansu
"twardość" reguluje się następującymi wartościami:
b2DistanceJointDef jointDef;
jointDef.Initialize( myBodyA, myBodyB, myBodyA->GetWorldCenter(), myBodyB->GetWorldCenter() );
jointDef.collideConnected = true;
jointDef.frequencyHz = 4.0f;
jointDef.dampingRatio = 0.45f;
revolute joint - "przypięcie" jednego ciała do drugiego
Ten joint pozwala utrzymać jeden punkt danego ciała w tym samym miejscu, co inny w drugim ciele.
Innymi słowy, "przypszpila" on jakieś ciało do innego w danym punkcie.
Klasa ta udostępnia motor, który pozwala regulować wzajemny obrót tych dwóch ciał.
Możemy także ustalić limit - do jakiego kątu względem kątu początkowego mogą obrócić się ciała.
Zastosowaniem revolute jointów mogą być np. koła, wiatraki, łańcuchy.
b2RevoluteJointDef jointDef;
jointDef.Initialize( bodyA, bodyB, bodyB->GetWorldCenter() );
jointDef.bodyA = bodyA;
jointDef.bodyB = bodyB;
jointDef.localAnchorA = bodyA->GetLocalCenter();
jointDef.localAnchorB = bodyB->GetLocalCenter();
jointDef.collideConnected = false;
jointDef.enableLimit = true;
jointDef.referenceAngle = 75.5f;
jointDef.lowerAngle = 32.f;
jointDef.upperAngle = 89.5f;
jointDef.enableMotor = true;
jointDef.motorSpeed = 46.f;
jointDef.maxMotorTorque = 100.0f;
Punkty zaczepu nie muszą koniecznie być w miejscach, gdzie znajdują się fikstury ciała. Mogą znajdować się "gdzieś", ciała i tak będą połączone. Wtedy te ciała będą się poruszać jak dwie kulki na końcu sznurka, który w pewnym miejscu jest trzymany w powietrzu. W takim wypadku można włączyć parametr collideConnected |
prismatic joint - ruch po osi
Prismatic joint przypomina przypiętą do pierszego ciała szynę, po której może poruszać sie drugie.
W prismatic joint początkowo ciała udostępniają wspólny punkt, tak jak w revolute joint.
Jednak kąt między nimi jest stały. Ponadto dawana jest oś, względem której ciała mogą się poruszać.
Punkt przypięcia tu jest jeden - wyobraź sobie, że przez niego przechodzi dana oś i ciała poruszają się wzdłuż niej.
To wiązanie udostępnia motor, który ustawia prędkość poruszania się po szynie oraz limit - jak bardzo ciała mogą sie od siebie "rozjechać"
Zastosowaniem prismatic jointów mogą być np. windy, przesuwające się platformy, tłoki.
b2PrismaticJointDef jointDef;
b2Vec2 moveAxis( 1.0f, 0.0f );
jointDef.Initialize( bodyA, bodyB, b2Vec2( 35.f, 52.0f ), moveAxis );
jointDef.collideConnected = true;
jointDef.enableLimit = true;
jointDef.lowerTranslation = 0.0f;
jointDef.upperTranslation = 50.0f;
jointDef.enableMotor = true;
jointDef.motorSpeed = 30.0f;
jointDef.maxMotorForce = 300.0f;
pulley joint - układ ciężarków
Pulley joint tworzy układ ciężarków - im jeden jest niżej, tym drugi wyżej.
Ten joint ma w sumie 4 punkty zaczepu - 2 na symulowanych ciałach i 2 z których te ciała się "zawieszają".
Jeśli
d1 będzie długością pierwszego, a
d2 to dla jednego układu bloczków:
d1 + d2 * wp = constPojawia sie tu czynnik
wp - "przełożenie", czyli jak bardzo działanie na pierwszy ciężarek będzie miało wpływ na drugi
Np. dla
wp = 0.5 pociągnięcie ciężarka pierwszego o 2 metry w dół spowoduje podniesienie drugiego o 1 metr.
b2PulleyJointDef jointDef;
b2Vec2 anchor1 = bodyA->GetWorldPoint( 0.0f, - 1.0f );
b2Vec2 anchor2 = bodyB->GetWorldCenter();
b2Vec2 block1( 3.4f, 2.5f );
b2Vec2 block2( 5.4f, 2.5f );
float ratio = 1.0f;
jointDef.Initialize( bodyA, bodyB, block1, block2, anchor1, anchor2, ratio );
mouse joint - sprężyna
Mouse joint działa podobnie do distance joint'a, tylko że przyciąga
jedno ciało do dowolnego punktu w świecie.
Ponieważ ten joint działa tylko na jedno ciało, jako drugie powinno być podane dowolne istniejące statyczne ciało.
b2MouseJointDef jointDef;
jointDef.bodyA = myStaticBody;
jointDef.bodyB = body;
jointDef.target.Set( 0.5f, 24.f );
jointDef.maxForce = 300.0f;
jointDef.frequencyHz = 4.0f;
jointDef.dampingRatio = 0.5f;
joint->SetTarget( 0.45f, 34.f );
wheel joint - amortyzatory
Wheel joint to połączenie distance, revolute i prismatic jointa.
Pozwala on poruszać się po jednej osi, jednocześnie przyciągając go do pewnego punktu na tej osi. Ponadto pozwala mu sie swobodnie obracać.
To wiązanie powstało tylko w jednym celu: koła w samochodzie/rowerze/motocyklu.
Oto fragment realnej gry w którym tworzone jest zawieszenie do samochodu:
b2WheelJointDef spr;
spr.collideConnected = false;
spr.enableMotor = true;
spr.motorSpeed = 0.0f;
spr.dampingRatio = 4.0f;
spr.frequencyHz = 0.4f;
spr.maxMotorTorque = 1000.0f;
b2Vec2 axis( 0.0f, 1.0f );
spr.Initialize( carBody, leftWheelBody, carOriginPos + b2Vec2( 20.0f, 60.0f ), axis );
world.CreateJoint( & spr );
spr.Initialize( carBody, rightWheelBody, carOriginPos + b2Vec2( 90.0f, 60.0f ), axis );
world.CreateJoint( & spr );
Najlepszą techniką tworzenia takich konstrukcji jest najpierw stworzenie odpowiednich ciał w odpowiednich miejscach, a dopiero potem łączenie ich jointami. |
rope joint - lina
Rope joint ustala maksymalny dystans między dwoma punktami dwóch ciał.
b2RopeJointDef jointDef;
jointDef.bodyA = bodyA;
jointDef.bodyB = bodyB;
jointDef.localAnchorA = bodyA->GetLocalCenter();
jointDef.localAnchorB = bodyB->GetLocalCenter();
jointDef.maxLength = 30.0f;
Testbed
Nie będę zamieszczał tutaj jednego przykładu zbierającego wszystkie klasy zaprezentowane tutaj.
Natomiast w paczce z Box2d otrzymujemy kod bardzo fajnego programu o nazwie Testbed.
Po kompilacji pozwala testować, bawić się różnymi aspektami Box2d.
Natomiast gorąco polecam analizę kodu źródłowego tego programu.
Wskazówki optymalizacyjne
1. Jeśli kilka obiektów będzie się poruszać razem, utwórz je w ramach jednego ciała.
2. Nie twórz ciała w punkcie (0, 0), a potem dopiero go przesuwaj. Od razu pola
position struktury
b2BodyDef.
3. Wszystkie statyczne fikstury stwórz w ramach jednego ciała statycznego.
4. Użyj metody
b2Body::SetSleep(), jeśli chwilowo nie potrzebujesz symulować danego ciała.
Linki zewnętrzne
Informacje - anglojęzyczne
www.box2d.org/manual.html - Oficjalny manual. Podstawowe źródło informacji o Box2d
kubawal.cba.pl/box2dapi/2.3/html/API/annotated.html - dokumentacja wersji 2.3 (taką samą otrzymujemy w downloadzie, ale w razie problemów wrzuciłem ją na mój serwer :) )
www.iforce2d.net/b2tut/ - Najbardziej rozbudowany kurs internetowy o Box2d
http://www.emanueleferonato.com/category/box2d/ - ciekawy blog o Box2d. Przykłady prezentowane są w ActionScript'cie, ale z przełożeniem na c++ nie powinno być problemów :)
Informacje - polskojęzyczne
Ze znalezieniem takowych miałem spore problemy, ale znalazłem parę rzeczy:
http://pawel1503.cba.pl/index.php/660/integracja-sfmla-i-box2d-czyli-kilka-wskazowek-dla-poczatkujacych/ - artykuł o integracji Box2d i SFML. Użyte wersja Box2d i SFML nie są najnowszego wydania, a ponadto uważam, że sposób prezentowany przez autora jest raczej słaby, ale to zawsze coś :)
http://software.com.pl/box2d-fizyczny-swiat-w-pudelku/ - rozbudowany, lecz odrobinkę przestarzały tutorial.
Programy i biblioteki wspomagające Box2d
Box2D JSON Loader:
http://www.iforce2d.net/b2djson/ - pozwala odczytywać i zapisywać całe symulacje do plików JSON. Niezwykle przydatne przy dużych światach.
R.U.B.E Editor:
http://www.iforce2d.net/rube-free/ - darmowy edytor WYSIWYG dla Box2D. Pozwala szybko stworzyć świat po czym przekonwertować go na kod c++ który stworzy to, co widzisz w edytorze.
R.U.B.E Premium:
http://www.iforce2d.net/rube/ - komercyjna wersja powyższego. Nie testowałem osobiście, ale user feedback jest podobno znakomity :)