1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
|
# Hello Box2D
In the distribution of Box2D is a Hello World project. The program
creates a large ground box and a small dynamic box. This code does not
contain any graphics. All you will see is text output in the console of
the box's position over time.
This is a good example of how to get up and running with Box2D.
## Creating a World
Every Box2D program begins with the creation of a b2World object.
b2World is the physics hub that manages memory, objects, and simulation.
You can allocate the physics world on the stack, heap, or data section.
It is easy to create a Box2D world. First, we define the gravity vector.
```cpp
b2Vec2 gravity(0.0f, -10.0f);
```
Now we create the world object. Note that we are creating the world on
the stack, so the world must remain in scope.
```cpp
b2World world(gravity);
```
So now we have our physics world, let's start adding some stuff to it.
## Creating a Ground Box
Bodies are built using the following steps:
1. Define a body with position, damping, etc.
2. Use the world object to create the body.
3. Define fixtures with a shape, friction, density, etc.
4. Create fixtures on the body.
For step 1 we create the ground body. For this we need a body
definition. With the body definition we specify the initial position of
the ground body.
```cpp
b2BodyDef groundBodyDef;
groundBodyDef.position.Set(0.0f, -10.0f);
```
For step 2 the body definition is passed to the world object to create
the ground body. The world object does not keep a reference to the body
definition. Bodies are static by default. Static bodies don't collide
with other static bodies and are immovable.
```cpp
b2Body* groundBody = world.CreateBody(&groundBodyDef);
```
For step 3 we create a ground polygon. We use the SetAsBox shortcut to
form the ground polygon into a box shape, with the box centered on the
origin of the parent body.
```cpp
b2PolygonShape groundBox;
groundBox.SetAsBox(50.0f, 10.0f);
```
The SetAsBox function takes the **half**-**width** and
**half**-**height** (extents). So in this case the ground box is 100
units wide (x-axis) and 20 units tall (y-axis). Box2D is tuned for
meters, kilograms, and seconds. So you can consider the extents to be in
meters. Box2D generally works best when objects are the size of typical
real world objects. For example, a barrel is about 1 meter tall. Due to
the limitations of floating point arithmetic, using Box2D to model the
movement of glaciers or dust particles is not a good idea.
We finish the ground body in step 4 by creating the shape fixture. For
this step we have a shortcut. We do not have a need to alter the default
fixture material properties, so we can pass the shape directly to the
body without creating a fixture definition. Later we will see how to use
a fixture definition for customized material properties. The second
parameter is the shape density in kilograms per meter squared. A static
body has zero mass by definition, so the density is not used in this
case.
```cpp
groundBody->CreateFixture(&groundBox, 0.0f);
```
Box2D does not keep a reference to the shape. It clones the data into a
new b2Shape object.
Note that every fixture must have a parent body, even fixtures that are
static. However, you can attach all static fixtures to a single static
body.
When you attach a shape to a body using a fixture, the shape's
coordinates become local to the body. So when the body moves, so does
the shape. A fixture's world transform is inherited from the parent
body. A fixture does not have a transform independent of the body. So we
don't move a shape around on the body. Moving or modifying a shape that
is on a body is not supported. The reason is simple: a body with
morphing shapes is not a rigid body, but Box2D is a rigid body engine.
Many of the assumptions made in Box2D are based on the rigid body model.
If this is violated many things will break
## Creating a Dynamic Body
So now we have a ground body. We can use the same technique to create a
dynamic body. The main difference, besides dimensions, is that we must
establish the dynamic body's mass properties.
First we create the body using CreateBody. By default bodies are static,
so we should set the b2BodyType at construction time to make the body
dynamic.
```cpp
b2BodyDef bodyDef;
bodyDef.type = b2_dynamicBody;
bodyDef.position.Set(0.0f, 4.0f);
b2Body* body = world.CreateBody(&bodyDef);
```
> **Caution**:
> You must set the body type to b2_dynamicBody if you want the body to
> move in response to forces.
Next we create and attach a polygon shape using a fixture definition.
First we create a box shape:
```cpp
b2PolygonShape dynamicBox;
dynamicBox.SetAsBox(1.0f, 1.0f);
```
Next we create a fixture definition using the box. Notice that we set
density to 1. The default density is zero. Also, the friction on the
shape is set to 0.3.
```cpp
b2FixtureDef fixtureDef;
fixtureDef.shape = &dynamicBox;
fixtureDef.density = 1.0f;
fixtureDef.friction = 0.3f;
```
> **Caution**:
> A dynamic body should have at least one fixture with a non-zero density.
> Otherwise you will get strange behavior.
Using the fixture definition we can now create the fixture. This
automatically updates the mass of the body. You can add as many fixtures
as you like to a body. Each one contributes to the total mass.
```cpp
body->CreateFixture(&fixtureDef);
```
That's it for initialization. We are now ready to begin simulating.
## Simulating the World
So we have initialized the ground box and a dynamic box. Now we are
ready to set Newton loose to do his thing. We just have a couple more
issues to consider.
Box2D uses a computational algorithm called an integrator. Integrators
simulate the physics equations at discrete points of time. This goes
along with the traditional game loop where we essentially have a flip
book of movement on the screen. So we need to pick a time step for
Box2D. Generally physics engines for games like a time step at least as
fast as 60Hz or 1/60 seconds. You can get away with larger time steps,
but you will have to be more careful about setting up the definitions
for your world. We also don't like the time step to change much. A
variable time step produces variable results, which makes it difficult
to debug. So don't tie the time step to your frame rate (unless you
really, really have to). Without further ado, here is the time step.
```cpp
float timeStep = 1.0f / 60.0f;
```
In addition to the integrator, Box2D also uses a larger bit of code
called a constraint solver. The constraint solver solves all the
constraints in the simulation, one at a time. A single constraint can be
solved perfectly. However, when we solve one constraint, we slightly
disrupt other constraints. To get a good solution, we need to iterate
over all constraints a number of times.
There are two phases in the constraint solver: a velocity phase and a
position phase. In the velocity phase the solver computes the impulses
necessary for the bodies to move correctly. In the position phase the
solver adjusts the positions of the bodies to reduce overlap and joint
detachment. Each phase has its own iteration count. In addition, the
position phase may exit iterations early if the errors are small.
The suggested iteration count for Box2D is 8 for velocity and 3 for
position. You can tune this number to your liking, just keep in mind
that this has a trade-off between performance and accuracy. Using fewer
iterations increases performance but accuracy suffers. Likewise, using
more iterations decreases performance but improves the quality of your
simulation. For this simple example, we don't need much iteration. Here
are our chosen iteration counts.
```cpp
int32 velocityIterations = 6;
int32 positionIterations = 2;
```
Note that the time step and the iteration count are completely
unrelated. An iteration is not a sub-step. One solver iteration is a
single pass over all the constraints within a time step. You can have
multiple passes over the constraints within a single time step.
We are now ready to begin the simulation loop. In your game the
simulation loop can be merged with your game loop. In each pass through
your game loop you call b2World::Step. Just one call is usually enough,
depending on your frame rate and your physics time step.
The Hello World program was designed to be simple, so it has no
graphical output. The code prints out the position and rotation of the
dynamic body. Here is the simulation loop that simulates 60 time steps
for a total of 1 second of simulated time.
```cpp
for (int32 i = 0; i < 60; ++i)
{
world.Step(timeStep, velocityIterations, positionIterations);
b2Vec2 position = body->GetPosition();
float angle = body->GetAngle();
printf("%4.2f %4.2f %4.2f\n", position.x, position.y, angle);
}
```
The output shows the box falling and landing on the ground box. Your
output should look like this:
```
0.00 4.00 0.00
0.00 3.99 0.00
0.00 3.98 0.00
...
0.00 1.25 0.00
0.00 1.13 0.00
0.00 1.01 0.00
```
## Cleanup
When a world leaves scope or is deleted by calling delete on a pointer,
all the memory reserved for bodies, fixtures, and joints is freed. This
is done to improve performance and make your life easier. However, you
will need to nullify any body, fixture, or joint pointers you have
because they will become invalid.
|