You are on page 1of 6

9/4/2020 Buoyancy - Box2D tutorials - iforce2d

iforce2d <# insert awesome header graphics #>

Crushing immovable objects since 2011

Box2D tutorials | Cocos2d-x resources | Box2D JSON loader | R.U.B.E Box2D editor | R.U.B.E free | My apps | Blog ! | 日本語はこちら | Box2D中文教程

News Mar 20: R.U.B.E v1.7 released. Instanciable objects!


Feb 21: Added RUBE sample loader for Cocos2d-X v3.4.
Feb 17: Funny Fists released in iOS App store and Google Play store. (YouTube video)
2014
May 25: R.U.B.E v1.6 released. Samplers!
May 12: "On-the-fly" resource updating for Cocos2d-x
Apr 22: New blog post: Inverted pendulum control
Apr 7: On-screen logging class for Cocos2d-x
Mar 22: Downhill Supreme 2 released for iOS and Android
Jan 2: YouTube video: Making soft-body wheels in RUBE
2013
Oct 6: Check out Supplyfront RTS, my 7dRTS entry continued.
Sep 26: R.U.B.E v1.5 released. Customizable item labels, snap-to-grid, export filtering, full-text help search.
Sep 18: Added RUBE sample loader for Cocos2d-X.
More...

Box2D tutorial topics


Box2D C++ tutorials - Buoyancy
Last edited: July 14 2013
Introduction
Setting up (Linux)
Setting up (Windows)
Halt! You should have a good understanding of
Setting up (Mac OSX) the basic tutorials before venturing further.
Testbed structure
Making a test
Bodies
Fixtures
Buoyancy
World settings Buoyancy causes things to be pushed in the opposite direction to gravity. This happens when they are less dense than the
Forces and impulses fluid they are in, like an apple in water, or a helium balloon in air (yes, technically air is a fluid :) The strength of the
Custom gravity buoyancy force depends on the mass of fluid displaced.
Moving at constant speed
We can simulate this effect in Box2D by finding the displaced fluid mass, and applying a force equal and opposite to the
Rotating to a given angle
gravity that would have acted on that mass. The displaced mass will be the area of fluid displaced, multiplied by its density.
Jumping So the main task is finding the area of overlap between the fluid and the floating object.
Using debug draw
Drawing your own objects We can use sensor fixtures to represent water, and calculate the overlapping portion between the water and other fixtures. A
User data full implementation of this would require an algorithm to calculate the intersection between two polygons, between two
circles, and between a polygon and a circle. In this topic we will just look at intersection of two polygons.
Anatomy of a collision
Collision callbacks Once the overlapping portion between two polygons has been found, we can use its area to calculate the necessary force, and
Collision filtering apply that force at the centroid of of the intersecting area. We will also need to apply some drag to stop it bouncing around
Sensors forever.
Ray casting
World querying
Removing bodies safely
The 'can I jump' question
Ghost vertices
Joints - overview
Joints - revolute
Joints - prismatic

FAQ / gotchas

Tutorial source code

Advanced topics

Conveyor belts
Projected trajectory
Sticky projectiles
Hovercar suspension
Top-down car physics
One-way walls
Buoyancy Buoyancy forces need to be calculated and applied every time step. We will use the BeginContact and EndContact functions of
Explosions the collision listener to keep track of a list of overlapping fixture pairs, and every time step we will run through that list and
Physics-driven particles see what forces are necessary. It's handy to typedef a pair of fixtures like this:

Other topics 1 typedef std::pair<b2Fixture*, b2Fixture*> fixturePair;

Setting up (iPhone) The list of overlapping fixtures only needs to include those that are relevant to buoyancy, so we can set some conditions on
what types of fixtures get put into the list. For example we might want something like this - one fixture should be a sensor,
and the other should be on a dynamic body:

https://www.iforce2d.net/b2dtut/buoyancy 1/6
9/4/2020 Buoyancy - Box2D tutorials - iforce2d
1 //class member variable
2 std::set<fixturePair> m_fixturePairs;
3
4 void BeginContact(b2Contact* contact)
5 {
6 b2Fixture* fixtureA = contact->GetFixtureA();
7 b2Fixture* fixtureB = contact->GetFixtureB();
8
9 if ( fixtureA->IsSensor() && fixtureB->GetBody()->GetType() == b2_dynamicBody )
10 m_fixturePairs.insert( make_pair(fixtureA, fixtureB) );
11 else if ( fixtureB->IsSensor() && fixtureA->GetBody()->GetType() == b2_dynamicBody )
12 m_fixturePairs.insert( make_pair(fixtureB, fixtureA) );
13 }
14
15 void EndContact(b2Contact* contact)
16 {
17 b2Fixture* fixtureA = contact->GetFixtureA();
18 b2Fixture* fixtureB = contact->GetFixtureB();
19
20 if ( fixtureA->IsSensor() && fixtureB->GetBody()->GetType() == b2_dynamicBody )
21 m_fixturePairs.erase( make_pair(fixtureA, fixtureB) );
22 else if ( fixtureB->IsSensor() && fixtureA->GetBody()->GetType() == b2_dynamicBody )
23 m_fixturePairs.erase( make_pair(fixtureB, fixtureA) );
24 }

Note that the BeginContact/EndContact are almost the same, the only difference is the insert/erase. Also important to note is
that we always make the water fixture the first member of the pair, and the floating object is the second member. This will be
important later.

Of course in a real game you would probably not want to make every sensor a water fixture, so the exact conditions used will
depend on your situation. Once we have obtained a list of the currently overlapping and relevant fixture pairs, we can go
through that list every time step and do the calculations for their overlapping area.

Intersection of two polygons

Calculating the overlapping portion between two polygons is not a trivial procedure, and although it is a core part of this topic
it is not really the main focus. There are many established methods for finding the intersection, and a good place to start is
Sutherland-Hodgman polygon clipping, which has source code for a common implementation in many languages. For this
topic I took the Javascript version on that page and translated it to C++ to make a function like this:

1 bool findIntersectionOfFixtures(b2Fixture* fA, b2Fixture* fB, vector<b2Vec2>& outputVertices);

My implementation only handles polygon-vs-polygon fixture pairs. The returned vertices for the overlapping area are in world
coordinates. Click here to see the full source code. Other methods may be faster, so choose the one that suits your situation
best. For example if your water surface only needs to be a single horizontal line instead of a fixture/region, you may be able
to take some shortcuts.

Area and centroid of a polygon

Likewise, calculating the area and centroid of a polygon is not the focus of this topic. Box2D includes a handy function for
computing a polygon centroid, which also happens to calculate the area, so we can re-use that with a couple of tiny
modifications. The function signature I ended up with is:

1 b2Vec2 ComputeCentroid(vector<b2Vec2> vs, float& area);

Click here to see the full source code.

Updating each time step

Once we have the groundwork ready for processing the fixture pairs, we can set up a loop which goes through them every
time step:

1 //inside Step(), loop through all currently overlapping fixture pairs


2 set<fixturePair>::iterator it = m_fixturePairs.begin();
3 set<fixturePair>::iterator end = m_fixturePairs.end();
4 while (it != end) {
5
6 //fixtureA is the fluid
7 b2Fixture* fixtureA = it->first;
8 b2Fixture* fixtureB = it->second;
9
10 float density = fixtureA->GetDensity();
11
12 vector<b2Vec2> intersectionPoints;
13 if ( findIntersectionOfFixtures(fixtureA, fixtureB, intersectionPoints) ) {
14
15 //find centroid
16 float area = 0;
17 b2Vec2 centroid = ComputeCentroid( intersectionPoints, area);
18
19 //apply buoyancy stuff here...
20 }
21
22 ++it;
23 }
https://www.iforce2d.net/b2dtut/buoyancy 2/6
9/4/2020 Buoyancy - Box2D tutorials - iforce2d
23 }

Applying buoyancy force

Once we have the overlapping area and centroid for a polygon pair, applying the buoyancy force is dead simple. Multiplying
the overlapping area by the density of the fluid gives us the displaced mass, which we can then multiply by gravity to get the
force:

1 b2Vec2 gravity( 0, -10 );


2
3 //apply buoyancy force (fixtureA is the fluid)
4 float displacedMass = fixtureA->GetDensity() * area;
5 b2Vec2 buoyancyForce = displacedMass * -gravity;
6 fixtureB->GetBody()->ApplyForce( buoyancyForce, centroid );

Applying simple drag

At this stage, floating bodies will be pushed upwards when they overlap the fluid, but with nothing to slow them down they
keep getting faster until they actually jump completely out of the fluid, then fall down, and jump back out, and so on forever.
What we need is some drag force to slow these bodies down while they are in the fluid.

We can create a simple drag by applying a force to the body in the opposite direction to the current movement, at the
centroid position. As we can see from the drag equation, the magnitude of the drag force should be proportional to the
density of the fluid and the square of the velocity.

1 //find relative velocity between object and fluid


2 b2Vec2 velDir = fixtureB->GetBody()->GetLinearVelocityFromWorldPoint( centroid ) -
3 fixtureA->GetBody()->GetLinearVelocityFromWorldPoint( centroid );
4 float vel = velDir.Normalize();
5
6 //apply simple linear drag
7 float dragMag = fixtureA->GetDensity() * vel * vel;
8 b2Vec2 dragForce = dragMag * -velDir;
9 fixtureB->GetBody()->ApplyForce( dragForce, centroid );

We can also apply some angular drag to prevent the floating bodies from rotating forever:

1 //apply simple angular drag


2 float angularDrag = area * -fixtureB->GetBody()->GetAngularVelocity();
3 fixtureB->GetBody()->ApplyTorque( angularDrag );

Better drag

The drag shown above is quite primitive. It makes no difference what shape the floating body is, the drag will always be the
same for a given area. We can make a much better calculation for the drag by looking at the individual edges which are
actually causing the drag. For instance, we could look at the leading edges of the body as it moves through the fluid, because
it is their area which resists the movement.

We can use the dot product of the normal of each edge to see whether it is facing the direction of travel, and how much area
it presents as resistance:

1 // l d t l f h l d
https://www.iforce2d.net/b2dtut/buoyancy 3/6
9/4/2020 Buoyancy - Box2D tutorials - iforce2d
1 //apply drag separately for each polygon edge
2 for (int i = 0; i < intersectionPoints.size(); i++) {
3 //the end points and mid-point of this edge
4 b2Vec2 v0 = intersectionPoints[i];
5 b2Vec2 v1 = intersectionPoints[(i+1)%intersectionPoints.size()];
6 b2Vec2 midPoint = 0.5f * (v0+v1);
7
8 //find relative velocity between object and fluid at edge midpoint
9 b2Vec2 velDir = fixtureB->GetBody()->GetLinearVelocityFromWorldPoint( midPoint ) -
10 fixtureA->GetBody()->GetLinearVelocityFromWorldPoint( midPoint );
11 float vel = velDir.Normalize();
12
13 b2Vec2 edge = v1 - v0;
14 float edgeLength = edge.Normalize();
15 b2Vec2 normal = b2Cross(-1,edge); //gets perpendicular vector
16
17 float dragDot = b2Dot(normal, velDir);
18 if ( dragDot < 0 )
19 continue; //normal points backwards - this is not a leading edge
20
21 float dragMag = dragDot * edgeLength * density * vel * vel;
22 b2Vec2 dragForce = dragMag * -velDir;
23 fixtureB->GetBody()->ApplyForce( dragForce, midPoint );
24 }

Now that we are applying many smaller forces around the body and taking into account the actual surface areas contributing
to the drag, we start to see some more realistic looking effects. For example, these two shapes should slow down at very
different rates after falling directly downwards into the fluid, right?

Lift

Lift is the force generated perpendicular to the direction of movement. Our simulation is looking reasonably good already with
just drag, but without applying any lift force we are missing some nice touches that give extra realism. Consider the situation
below - if these two bodies were pushed toward the right, they both present about the same surface area in the direction of
travel to resist the movement. So with our current drag force, the resulting effect will be about the same... but that doesn't
feel right at all.

Our natural sense tells us that the body on the left should tend to rise upward as it moves, due to the shape of its leading
edge. As another example, consider the case below where a plank of wood is held diagonally in a stream of water, or a
sailboat is catching wind in its sail. Drag pushes the plank downstream, and lift pushes it sideways. (Lift is not necessarily an
upwards force, even though the name kinda sounds like it.)

https://www.iforce2d.net/b2dtut/buoyancy 4/6
9/4/2020 Buoyancy - Box2D tutorials - iforce2d

From searching around the net a while, it seems that what we're dealing with here is known as flat plate aerodynamics (or
hydrodynamics). The lift should be zero when the plank is at right angles to the stream and highest somewhere in-between,
with the maximum force at 45 degrees. You could consider this to be proportional to the area of the rectangle that the plank
is a diagonal of.

To put this into a calculation, I have multiplied the dot product that we had earlier (the edge normal and the direction of
movement) with the dot product of the edge direction and the direction of movement. This effectively becomes cos(angle) *
cos(90-angle). One of these is zero at zero degrees, and the other is zero at ninety degrees, and they are both non-zero in
between.

So for each edge, we add another force in the perpendicular direction. Like drag, lift is also proportional to the density of fluid
and the square of the velocity:

1 //apply lift
2 float liftDot = b2Dot(edge, velDir);
3 float liftMag = (dragDot * liftDot) * edgeLength * density * vel * vel;
4 b2Vec2 liftDir = b2Cross(1,velDir); //gets perpendicular vector
5 b2Vec2 liftForce = liftMag * liftDir;
6 fixtureB->GetBody()->ApplyForce( liftForce, midPoint );

Now we should get a better effect when the leading edge is at an angle relative to the direction of movement.

Potential problems

For most simple shapes the approach shown here will work quite well, but when you have a body made from many fixtures
the 'leading' edges inside them might cause trouble. Making a body from multiple fixtures is necessary when the desired
overall shape is concave, but it can also be necessary for convex surfaces which have more than Box2D's default maximum of
8 vertices. Let's suppose the boat from the example above was made from two individual fixtures.

Can you see the problem here? With our current calculations the fixture at the rear of the boat has a leading edge which will
be used to generate drag and lift. The lift will push the boat downwards as it moves right, and the overall drag will be about
twice as strong as it should be. To fix this problem you would need to use a polygon that describes the overall boat hull rather
than using the individual Box2D fixtures within it.

Source code

Here is the source code for those who would like to try it out for themselves. This is a 'test' for the testbed, based on Box2D
https://www.iforce2d.net/b2dtut/buoyancy 5/6
9/4/2020 Buoyancy - Box2D tutorials - iforce2d
e e s t e sou ce code o t ose o ou d e to t y t out o t e se es s s a test o t e testbed, based o o
v2.3.0.

Testbed test: iforce2d_Buoyancy.zip

Linux 32-bit binary


Linux 64-bit binary
Windows binary
MacOSX binary

YouTube video
RUBE files for the scenes in the video: buoyancyRUBEScenes.zip

Not seeing the any comments below? Try changing the 'https' in the page URL to just 'http'
Click here: http://www.iforce2d.net/b2dtut/buoyancy

Want to promote your Box2D game here for free?


Contact gmail 'iforce2d'
If you find this site useful please consider supporting it :)

https://www.iforce2d.net/b2dtut/buoyancy 6/6

You might also like