Managing multiple gravities with Box2D

One of the apparently hardest things to do with Box2D is assigning different gravity forces to different objects.

For instance, you may want the world to be ruled with regular gravity, while you don’t want the bullet fired by your character to be affected, or you may want regular gravity for all boxes and inverted one for all circles, like in the example I am talking about.

There is one way to simulate different gravity and one way to apply different gravity in your Box2D projects.

This is the main script, directly from the HelloWorld.as example provided with Box2D distribution (so just copy/paste the code)

In some case you should need to refresh the page to see the movies in action.

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
package {
	import flash.display.Sprite;
	import flash.events.Event;
	import Box2D.Dynamics.*;
	import Box2D.Collision.*;
	import Box2D.Collision.Shapes.*;
	import Box2D.Common.Math.*;
	public class HelloWorld extends Sprite {
		public var m_world:b2World;
		public var m_iterations:int=10;
		public var m_timeStep:Number=1.0/30.0;
		public function HelloWorld() {
			addEventListener(Event.ENTER_FRAME, Update, false, 0, true);
			var worldAABB:b2AABB = new b2AABB();
			worldAABB.lowerBound.Set(-100.0, -100.0);
			worldAABB.upperBound.Set(100.0, 100.0);
			var gravity:b2Vec2=new b2Vec2(0.0,10.0);
			var doSleep:Boolean=true;
			m_world=new b2World(worldAABB,gravity,doSleep);
			var dbgDraw:b2DebugDraw = new b2DebugDraw();
			var dbgSprite:Sprite = new Sprite();
			addChild(dbgSprite);
			dbgDraw.m_sprite=dbgSprite;
			dbgDraw.m_drawScale=30.0;
			dbgDraw.m_fillAlpha=0.5;
			dbgDraw.m_lineThickness=1.0;
			dbgDraw.m_drawFlags=b2DebugDraw.e_shapeBit;
			m_world.SetDebugDraw(dbgDraw);
			var body:b2Body;
			var bodyDef:b2BodyDef;
			var boxDef:b2PolygonDef;
			var circleDef:b2CircleDef;
			bodyDef = new b2BodyDef();
			bodyDef.position.Set(10, 12);
			boxDef = new b2PolygonDef();
			boxDef.SetAsBox(30, 0.5);
			boxDef.friction=0.3;
			boxDef.density=0;
			body=m_world.CreateBody(bodyDef);
			body.CreateShape(boxDef);
			body.SetMassFromShapes();
			bodyDef = new b2BodyDef();
			bodyDef.position.Set(10, 0);
			boxDef = new b2PolygonDef();
			boxDef.SetAsBox(30, 0.5);
			boxDef.friction=0.3;
			boxDef.density=0;
			body=m_world.CreateBody(bodyDef);
			body.CreateShape(boxDef);
			body.SetMassFromShapes();
			for (var i:int = 1; i < 10; i++) {
				bodyDef = new b2BodyDef();
				bodyDef.position.x=Math.random()*12+2;
				bodyDef.position.y=Math.random()+5;
				var rX:Number=Math.random()+0.2;
				var rY:Number=Math.random()+0.2;
				if (Math.random()<0.5) {
					boxDef = new b2PolygonDef();
					boxDef.SetAsBox(rX, rY);
					boxDef.density=1.0;
					boxDef.friction=0.5;
					boxDef.restitution=0.2;
					bodyDef.userData = new Sprite();
					bodyDef.userData.name="box";
					body=m_world.CreateBody(bodyDef);
					body.CreateShape(boxDef);
				} else {
					circleDef = new b2CircleDef();
					circleDef.radius=rX;
					circleDef.density=1.0;
					circleDef.friction=0.5;
					circleDef.restitution=0.2;
					bodyDef.userData = new Sprite();
					bodyDef.userData.name="circle";
					body=m_world.CreateBody(bodyDef);
					body.CreateShape(circleDef);
				}
				body.SetMassFromShapes();
				addChild(bodyDef.userData);
			}
		}
		public function Update(e:Event):void {
			m_world.Step(m_timeStep, m_iterations);
		}
	}
}

The only interesting lines are line 17 where I declare the gravity vector and lines 64 and 74 where I assign names to the objects… box for boxes and circle for circles

And this is the result:

As you can see, the world is ruled by standard gravity, but as I said I want boxes to be ruled by normal gravity and circles to be ruled by inverted gravity

Now, let’s see the first method, called (by myself)….

The antagonist forces method

As the name means, the principle is applying an antagonist force to circles in order to simulate a reverse gravity. Since the default gravity is (0,10), I am going to apply a (0,-20) force to all circles.

This is the updated Update function:

82
83
84
85
86
87
88
89
90
91
92
93
public function Update(e:Event):void {
	var ant_gravity = b2Vec2;
	m_world.Step(m_timeStep, m_iterations);
	for (var bb:b2Body = m_world.m_bodyList; bb; bb = bb.m_next) {
		if (bb.GetUserData()!=null) {
			if (bb.GetUserData().name=="circle") {
				ant_gravity = new b2Vec2(0.0,-20.0*bb.GetMass());
				bb.ApplyForce(ant_gravity,bb.GetWorldCenter());
			}
		}
	}
}

And this is the result:

The result is what we want, anyway now you may say to create objects that aren’t affected by gravity you just have to change line 88 with

ant_gravity = new b2Vec2(0.0,-10.0*bb.GetMass());

but look at the result…

circles are slowly falling down. This happens because when you call Step functions, bodies have an acceleration due to gravity, and the opposite force isn’t enough to nullify it.

Let’s see the second method, called

The island method

This method is named after the filename of the library you have to edit… it’s b2Island.as inside Dynamics folder.

First, the only line inside Update function must be once again

m_world.Step(m_timeStep, m_iterations);

as in the first example.

Then, this is the modified version of Solve function inside b2Island.as file:

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
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
public function Solve(step:b2TimeStep, gravity:b2Vec2, correctPositions:Boolean, allowSleep:Boolean) : void
{
	var i:int;
	var b:b2Body;
	var joint:b2Joint;
	var applied_gravity:b2Vec2
 
	// Integrate velocities and apply damping.
	for (i = 0; i < m_bodyCount; ++i)
	{
		b = m_bodies[i];
 
		if (b.IsStatic())
			continue;
 
		// Integrate velocities.
		//b.m_linearVelocity += step.dt * (gravity + b.m_invMass * b.m_force);
		if(b.m_userData.name=="circle"){
			applied_gravity = new b2Vec2(-gravity.x,-gravity.y)
		}
		else{
			applied_gravity = gravity;
		}
		b.m_linearVelocity.x += step.dt * (applied_gravity.x + b.m_invMass * b.m_force.x);
		b.m_linearVelocity.y += step.dt * (applied_gravity.y + b.m_invMass * b.m_force.y);
		b.m_angularVelocity += step.dt * b.m_invI * b.m_torque;
 
		// Reset forces.
		b.m_force.SetZero();
		b.m_torque = 0.0;
 
		// Apply damping.
		// ODE: dv/dt + c * v = 0
		// Solution: v(t) = v0 * exp(-c * t)
		// Time step: v(t + dt) = v0 * exp(-c * (t + dt)) = v0 * exp(-c * t) * exp(-c * dt) = v * exp(-c * dt)
		// v2 = exp(-c * dt) * v1
		// Taylor expansion:
		// v2 = (1.0f - c * dt) * v1
		b.m_linearVelocity.Multiply( b2Math.b2Clamp(1.0 - step.dt * b.m_linearDamping, 0.0, 1.0) );
		b.m_angularVelocity *= b2Math.b2Clamp(1.0 - step.dt * b.m_angularDamping, 0.0, 1.0);
 
		// Check for large velocities.
		//if (b2Dot(b->m_linearVelocity, b->m_linearVelocity) > b2_maxLinearVelocitySquared)
		if ((b.m_linearVelocity.LengthSquared()) > b2Settings.b2_maxLinearVelocitySquared)
		{
			b.m_linearVelocity.Normalize();
			b.m_linearVelocity.x *= b2Settings.b2_maxLinearVelocity;
			b.m_linearVelocity.y *= b2Settings.b2_maxLinearVelocity;
		}
 
		if (b.m_angularVelocity * b.m_angularVelocity > b2Settings.b2_maxAngularVelocitySquared)
		{
			if (b.m_angularVelocity < 0.0)
			{
				b.m_angularVelocity = -b2Settings.b2_maxAngularVelocity;
			}
			else
			{
				b.m_angularVelocity = b2Settings.b2_maxAngularVelocity;
			}
		}
	}
 
	var contactSolver:b2ContactSolver = new b2ContactSolver(step, m_contacts, m_contactCount, m_allocator);
 
	// Initialize velocity constraints.
	contactSolver.InitVelocityConstraints(step);
 
	for (i = 0; i < m_jointCount; ++i)
	{
		joint = m_joints[i];
		joint.InitVelocityConstraints(step);
	}
 
	// Solve velocity constraints.
	for (i = 0; i < step.maxIterations; ++i)
	{
		contactSolver.SolveVelocityConstraints();
 
		for (var j:int = 0; j < m_jointCount; ++j)
		{
			joint = m_joints[j];
			joint.SolveVelocityConstraints(step);
		}
	}
 
	// Post-solve (store impulses for warm starting).
	contactSolver.FinalizeVelocityConstraints();
 
	// Integrate positions.
	for (i = 0; i < m_bodyCount; ++i)
	{
		b = m_bodies[i];
 
		if (b.IsStatic())
			continue;
 
		// Store positions for continuous collision.
		b.m_sweep.c0.SetV(b.m_sweep.c);
		b.m_sweep.a0 = b.m_sweep.a;
 
		// Integrate
		//b.m_sweep.c += step.dt * b.m_linearVelocity;
		b.m_sweep.c.x += step.dt * b.m_linearVelocity.x;
		b.m_sweep.c.y += step.dt * b.m_linearVelocity.y;
		b.m_sweep.a += step.dt * b.m_angularVelocity;
 
		// Compute new transform
		b.SynchronizeTransform();
 
		// Note: shapes are synchronized later.
	}
 
	if (correctPositions)
	{
		// Initialize position constraints.
		// Contacts don't need initialization.
		for (i = 0; i < m_jointCount; ++i)
		{
			joint = m_joints[i];
			joint.InitPositionConstraints();
		}
 
		// Iterate over constraints.
		for (m_positionIterationCount = 0; m_positionIterationCount < step.maxIterations; ++m_positionIterationCount)
		{
			var contactsOkay:Boolean = contactSolver.SolvePositionConstraints(b2Settings.b2_contactBaumgarte);
 
			var jointsOkay:Boolean = true;
			for (i = 0; i < m_jointCount; ++i)
			{
				joint = m_joints[i];
				var jointOkay:Boolean = joint.SolvePositionConstraints();
				jointsOkay = jointsOkay && jointOkay;
			}
 
			if (contactsOkay && jointsOkay)
			{
				break;
			}
		}
	}
 
	Report(contactSolver.m_constraints);
 
	if (allowSleep){
 
		var minSleepTime:Number = Number.MAX_VALUE;
 
		var linTolSqr:Number = b2Settings.b2_linearSleepTolerance * b2Settings.b2_linearSleepTolerance;
		var angTolSqr:Number = b2Settings.b2_angularSleepTolerance * b2Settings.b2_angularSleepTolerance;
 
		for (i = 0; i < m_bodyCount; ++i)
		{
			b = m_bodies[i];
			if (b.m_invMass == 0.0)
			{
				continue;
			}
 
			if ((b.m_flags & b2Body.e_allowSleepFlag) == 0)
			{
				b.m_sleepTime = 0.0;
				minSleepTime = 0.0;
			}
 
			if ((b.m_flags & b2Body.e_allowSleepFlag) == 0 ||
				b.m_angularVelocity * b.m_angularVelocity > angTolSqr ||
				b2Math.b2Dot(b.m_linearVelocity, b.m_linearVelocity) > linTolSqr)
			{
				b.m_sleepTime = 0.0;
				minSleepTime = 0.0;
			}
			else
			{
				b.m_sleepTime += step.dt;
				minSleepTime = b2Math.b2Min(minSleepTime, b.m_sleepTime);
			}
		}
 
		if (minSleepTime >= b2Settings.b2_timeToSleep)
		{
			for (i = 0; i < m_bodyCount; ++i)
			{
				b = m_bodies[i];
				b.m_flags |= b2Body.e_sleepFlag;
				b.m_linearVelocity.SetZero();
				b.m_angularVelocity = 0.0;
			}
		}
	}
}

Let’s see the lines I added/changed:

Line 162: declaring a new vector that will handle the applied gravity

Line 174: if the body we are solving is called “circle”…

Line 175: set applied_gravity vector as the opposite of the default gravity one

Lines 177-179: If not, set applied_gravity vector to default gravity one

Lines 180-181: Use applied_gravity vector instead of gravity vector to determine body’s linear velocity

And this is the result:

That is working perfectly even if you change line 175 this way:

applied_gravity = new b2Vec2(0,0)

To have no gravity circles, so this last method is preferred.

Let me think what do you think about it.

Rate this post: 1 Star2 Stars3 Stars4 Stars5 Stars (31 votes, average: 4.74 out of 5)
Loading ... Loading ...
Flash Templates provided by Template Monster are pre-made web design products developed using Flash technology.
They can be easily customized to meet the unique requirements of your project.
Be my fan on Facebook and follow me on Twitter! Exclusive content for my Facebook fans and Twitter followers

This post has 18 comments

  1. Jordan

    on July 17, 2009 at 7:26 pm

    Emanuele, awesome post! Today I just so happened to need to figure out how to apply multiple gravities in Box2D… so it’s a bit of a weird/amazing coincidence that this is today’s post. Very helpful – thank you!

  2. C Legan

    on July 17, 2009 at 10:50 pm

    You could calculate the gravity ‘vector’ if you modified the algo to take a gravity ‘location’, and achieve anti-gravity effect through the use of negative mass.

  3. Josh of Cubicle Ninjas

    on July 18, 2009 at 12:00 am

    Brilliant.

  4. Unknown

    on July 18, 2009 at 2:59 am

    Awesome!

  5. Cyclonic

    on July 18, 2009 at 5:48 am

    Is it possible to do two-dimensional gravity (orbiting a planet, for example), or would you just have to set the gravity to zero and apply forces towards the source of gravity?

  6. Guest

    on July 18, 2009 at 7:29 pm

    Emanuele, can you use Ajax or something to reload/hide/show the flash examples so we don’t have to reload all the web pages to see the flash please?

  7. internetFluent

    on July 19, 2009 at 4:48 pm

    Great examples. Thanks ! I love Box2D and it’s great to see how others do things :) . Great blog BTW.

  8. Michael J Williams

    on July 20, 2009 at 2:31 am

    Really smart! Thanks, Emanuele.

  9.   Basic Filler engine with Box2D – part 2 by Blam Yo!

    on August 5, 2009 at 1:45 am

    [...] Obviously, enemies aren’t affected by gravity, and this can be made following the instructions published at Managing multiple gravities with Box2D. [...]

  10. rasel

    on August 5, 2009 at 6:24 am

    amazing idea :)

  11. WorldsCreator

    on August 13, 2009 at 7:50 pm

    Great!

    But how about creating, in the b2Body.as, two new property: useOwnGravity and gravity (with getters & setters)?

    Then in the b2Island.as you can check if the current body has useOwnGravity set to true and set the current gravity to the body gravity..

    es.
    b2Body.as

    public var _useOwnGravity:Boolean = false;
    public var _gravity:beVec2;

    public function set useOwnGravity(f:Boolean):void
    {
    this._useOwnGravity = f;
    }
    public function get useOwnGravity():Boolean
    {
    return this._useOwnGravity;
    }

    public function set gravity(g:b2Vec2):void
    {
    this._gravity = g;
    }
    public function get gravity():Boolean
    {
    return this._gravity;
    }

    in the b2Island.as just do this:

    if(b.useOwnGravity)
    {
    gravity = b.gravity;
    }

    I haven’t tried this yet but I will soon. Anyway…sorry for my poor english :)

  12. xMine

    on August 23, 2009 at 5:05 pm

    I’ve just found your blog and its great, thank you for all the box2d tuts!
    @WorldsCreator: Good Idea. I think that is the best idea to do this :)

  13. Andriy

    on September 18, 2009 at 3:00 pm

    At the beginnig graviti = 0;

    in Update() we applay gorces what we need. its can be 10 or -10;

  14. cmedina

    on November 3, 2009 at 10:27 pm

    I think you should try the “antagonist” (first one) method but apply the force to the body local center not to its world center.

    public function Update(e:Event):void {
    var ant_gravity = b2Vec2;
    m_world.Step(m_timeStep, m_iterations);
    for (var bb:b2Body = m_world.m_bodyList; bb; bb = bb.m_next) {
    if (bb.GetUserData()!=null) {
    if (bb.GetUserData().name==”circle”) {
    ant_gravity = new b2Vec2(0.0,-20.0*bb.GetMass());
    bb.ApplyForce(ant_gravity,bb.GetLocalCenter());//** CHANGED **//
    }
    }
    }
    }

  15. cmedina

    on November 4, 2009 at 2:47 pm

    Antagonist Method’s Problem Solved!!!
    The real problem is:
    Island is executing Solve before the update() is called. When Solve is called there is no “antagonist” force yet, so this causes a linear velocity. When the update() is called, then the force is attenuated completely but the linear velocity caused with the very first Solve() call is still there.

    The solution is:
    Apply the force right after the body creation AND on the update().

    Note: There is still a problem with this solution. Everytime you do a WakeUp() call to a body, a Solve() call is made with no “antagonist” force. Perhaps this is a Box2D bug.

  16. Box2D progress so far… | Random Miscalculations

    on June 10, 2010 at 10:31 pm

    [...] Multiple Gravities [...]

  17. Jimbo

    on June 18, 2010 at 5:32 am

    Hi, I am encountering some problems when I port this code promote to Box2dFlash 2.1alpha. Is there any help some one can offer?

  18. [box2d][as3] multiple gravity | CODE@????

    on May 3, 2011 at 2:04 pm

    [...] http://www.emanueleferonato.com/2009/07/17/managing-multiple-gravities-with-box2d/ This entry was posted on Tuesday, May 3rd, 2011 at 8:02 pmand is filed under AS3, ??. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site. [...]