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.
They can be easily customized to meet the unique requirements of your project.















(31 votes, average: 4.74 out of 5)









This post has 18 comments
Jordan
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!
C Legan
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.
Josh of Cubicle Ninjas
Brilliant.
Unknown
Awesome!
Cyclonic
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?
Guest
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?
internetFluent
Great examples. Thanks ! I love Box2D and it’s great to see how others do things :) . Great blog BTW.
Michael J Williams
Really smart! Thanks, Emanuele.
Basic Filler engine with Box2D – part 2 by Blam Yo!
[...] Obviously, enemies aren’t affected by gravity, and this can be made following the instructions published at Managing multiple gravities with Box2D. [...]
rasel
amazing idea :)
WorldsCreator
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 :)
xMine
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 :)
Andriy
At the beginnig graviti = 0;
in Update() we applay gorces what we need. its can be 10 or -10;
cmedina
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 **//
}
}
}
}
cmedina
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.
Box2D progress so far… | Random Miscalculations
[...] Multiple Gravities [...]
Jimbo
Hi, I am encountering some problems when I port this code promote to Box2dFlash 2.1alpha. Is there any help some one can offer?
[box2d][as3] multiple gravity | CODE@????
[...] 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. [...]