Game Development Community

Box2D Integration on Google Code

by Michael Woerister · in Torque Game Builder · 01/23/2009 (6:27 am) · 144 replies

Hi all,

as you may or may not know I created a wrapper for Box2D that let's you use it in TGB through script and behaviors. I wrote my bachelor thesis on this topic and now that it got approved I'd like to share the code with the community.

So here is my question to Garage Games: Is it OK if I put code and thesis up on Google Code (or Sourceforge)? It obviously contains some references to TGB's C++ code base and I don't want break the EULA. But having a public Subversion repository there would make it way easier to maintain the resource.

A few bits of information on the wrapper:
- It requires TGB Pro because Box2D is written in C++.
- It does not silently replace TGB's internal physics engine. It has to be used explicitly.
- It allows for binding t2dSceneObjects to Box2D bodies and shapes.
- I tried to make it as usable as possible in the time I had but it's not for sissies ;)
- It's a good idea to read the thesis to get an overview.

So, what do you say?

-Michael
#41
02/19/2009 (12:02 pm)
You have to actually define the symbol. Open the TGBGame project properties, go to "C/C++"->"Preprocessor". There should be a "#define" entry or something like that. It should already contain TORQUE_PLAYER (and maybe some other things). Add TORQUE_DISABLE_MEMORY_MANAGER there and recompile.

By the way, I would recommend using a debug build during developement. Otherwise you won't get all error messages.
#42
02/19/2009 (12:23 pm)
Finally !
Working :D
Great, thanks a lot for this support.

About my previous question on ContactPoint, what would you do ?
I would like to avoid C++ as much as possible but if TorqueScript can't handle them because of slowdown I don't know what to do...
#43
02/19/2009 (12:29 pm)
It really depends on what you want to do with the contact callbacks. What do you want to use them for?
#44
02/19/2009 (12:54 pm)
Currently in my engine for example Contact Point are collected into m_point array inside the implemented b2ContactListener.
Since I can recover the 2 colliding Objects from shapes (thanks to the get/set UserData() where I put instances of object) each cycle I process them all and call object.ProcessContactPoint() method which can decide what to do when a specific collision occur.

Doing the same thing would be awesome. But if you say that torqueScript is too slow to call methods for each contact point I suppose I need to cover this part with C++. Which is problematic since every Box2d objects/methods/functions is open to TorqueScript.

And most of all you probably see that I'm not at ease when using C++ inside TGB Gigantic Sources.
#45
02/19/2009 (3:38 pm)
Maybe Processing all ContactPoint at onFrameChange rather than onUpdate could cool off TGB when using Box2D via TorqueScript ?
Currently there is a (huge) maximum of 2048 MAX Contact Points in my engine but most of the time I am around 300 Contact Points. Cleaning the Contact Points array each frame at 60FPS is my default setup.

Any ideas ?
I still need to re-read and re-practice the whole TorqueScript documentations to choose the best solution.
#46
02/20/2009 (3:02 am)
Maybe you can somehow filter out contact points that don't really need to be processed (duplicates for example).

Another option might be to create contact-handler classes in C++ that can be accessed from TorqueScript. That way you can implement everything in script that is called not so often (configuration stuff) but the heavy work can run in C++ without any script interaction. That's probably what I would do.

Quote:Maybe Processing all ContactPoint at onFrameChange rather than onUpdate could cool off TGB when using Box2D via TorqueScript ?
No, that would not help. Box2D creates contact points during the b2World::step() call which is called in Box2dScene::onUpdateSceneTick(). onFrameChange() is called more often than onUpdateSceneTick() so you would either end up processing each contact point more than once or processing an empty contact list 10 times before it is filled again.
#47
02/20/2009 (6:16 am)
Ok, thanks.
This could work but need a constant C++ rebuild and I know that Callback Contact Point is a key to Box2d.
Do you know any references/examples to share objects created in TorqueScript then manipulated in C++ ?
Sound like an terrible solution when you don't know much "TGB C++" :(
A small example would be greatly appreciated.
#49
02/20/2009 (10:30 am)
Thanks a lot :)
Will study this and also that one : www.garagegames.com/community/forums/viewthread/75628

I still don't know how much C++ code will be involved in standard project but it still the best resources for C++ Guru.
#50
02/20/2009 (2:05 pm)
These documentations all covering C++ => TorqueScript CallBack and exposed Object but unfortunately not TorqueScript => C++

The only solution I know to determine which objects collide in Box2d is b2Shape::setUserData($objectInstance).
I suppose that if I put $objectInstance I only send is unique ID, so b2Shape::getUserData() in C++ cannot be cast in order for example to check :

IF it's a Ball colliding with Ground THEN Ball($ObjectInstance).DoBounce();

(The DoBounce Method and Ball Object would be entirely created in TorqueScript or then it would be easier to do everything in C++).

If I want to use your idea of contact-handler classes in C++, I need to know :
1) How to pass a full reference object into b2Shape::setUserData(*)
.. or find this object from C++ by using his unique ID
2) Cast TorqueScript Object in C++ and call TorqueScript Methods
#51
02/21/2009 (2:23 am)
1) You can use the Sim::findObject() function to get a point to a SimObject with a given id.

2) You can use the Con::executef() function to call TorqueScript methods from C++.

But mind that calling a TorqueScript function from C++ is as slow as calling it in script. I would rather implement anything in C++ that is called as a result of a contact callback.

As long as you don't use any advanced features of C++, it's not much more difficult than TorqueScript (except for handling strings maybe).
#52
02/21/2009 (5:43 am)
Thanks for these good help and advices.
Well, you probably right I should preferably fully implement contact callback on the C++ side.

I just find the "generateEngineDocs.bat" and "generateConsoleDocs.bat" doxygen as well as the docGenerator tools ( I LOVE when you get clear information on how to find the C++ doc) hope this will give me a good overview to the C++ API but I seriously doubt I will do something in this language since TGB source code is way too complicated for me.

I think I walk away from the original idea of Box2D and TGB : doing game more straight away and not focus on coding.
#53
05/02/2009 (3:17 am)
I've just tried to integrate that great resource two times into a clean TGB code base but failed.

I can run the Box2DTestFramework with my exe without problems but the TGB-Box2D-Integration sample fails at line 116 in Box2dWorldRef.cc:

AssertFatal( sceneGraph, "Box2dWorldRef::onAdd() - Invalid SceneGraph." );

Any idea what I'm doing wrong?
#54
05/02/2009 (5:13 am)
Hi, I was just able to reproduce this. Don't worry, the TGB-Box2D-Integration is just built against an older version of the integration. The Box2DTestFramework is the one up-to-date. So if it works there, everything should be fine. Sorry for the confusion.

I'll probably remove the current integration sample and replace it with Box2DTestFramework anyway.
#55
05/02/2009 (9:44 pm)
Thanks Michael, starting to play with Box2D now :)
#56
07/22/2009 (4:40 am)
In Box2dBodyBehavior.cs I have written:
if ( !isObject(Box2dBodyBehavior) )
{
new BehaviorTemplate(Box2dBodyBehavior);

Box2dBodyBehavior.behaviorType = "Box2D integration";
Box2dBodyBehavior.friendlyName = "Box2D Body Behavior";
Box2dBodyBehavior.description = "Makes the owning t2dSceneObject a Box2D body";

Box2dBodyBehavior.addBehaviorField( "Mass", "The mass of the object", float, 0.0);
Box2dBodyBehavior.addBehaviorField( "Inertia", "The inertia of the object", float, 0.0 );
Box2dBodyBehavior.addBehaviorField( "CenterOfMass", "The center of mass of the object", Point2F, "0.0 0.0" );
Box2dBodyBehavior.addBehaviorField( "AutoMass", "If true the object's mass/inertia/center is calculated automatically", bool, true );
Box2dBodyBehavior.addBehaviorField( "LinearDamping", "The linear damping coefficient of the object", float, 0.0 );
Box2dBodyBehavior.addBehaviorField( "AngularDamping", "The angular damping coefficient of the object", float, 0.0 );
Box2dBodyBehavior.addBehaviorField( "AllowSleeping", "Is this object allowed to sleep?", bool, true );
Box2dBodyBehavior.addBehaviorField( "IsSleeping", "Is the object sleeping initially?", bool, false );
Box2dBodyBehavior.addBehaviorField( "FixedRotation", "If true the object will not rotate", bool, false );
Box2dBodyBehavior.addBehaviorField( "IsBullet", "Better tunneling prevents", bool, false );
}

In LevelObject.cs I have written:
function makeLevelObject(%myParent, %CloneObjectName, %PositionX, %PositionY, %ObjectWidth, %ObjectHeight, %Rotation, %FlipX, %FlipY, %BodyBehavior, %ShapeBehavior)
{
%LevelObj = %CloneObjectName.clone(true);
$LastObjectSelected = %LevelObj;

%LevelObj.setPosition(%PositionX SPC %PositionY);
%LevelObj.myParent = %myParent;
%LevelObj.setSize(%ObjectWidth, %ObjectHeight);
%LevelObj.setVisible(true);
%LevelObj.useMouseEvts = true;
%LevelObj.Rotation = %Rotation;
%LevelObj.setRotation(%LevelObj.Rotation);
%LevelObj.setFlip(%FlipX, %FlipY);

%BodyInstance = %BodyBehavior.createInstance();
%LevelObj.addBehavior(%BodyInstance);
}

What I am trying to do:
I want to change the default value that is taken by the %LevelObj from Box2dBodyBehavior.
However this is not happening with the above code. The object still takes original default values.
#57
07/22/2009 (4:51 am)
Can you explain a little more what the makeLevelObject() function is meant to do and provide an example of how you call it?

#58
07/22/2009 (5:05 am)
Here is the code that is calling function makeLevelObject:
%newLevelObj = new ScriptObject() {
CloneObjectName = %name;
PositionX = 0;
PositionY = 0;
ObjectWidth = 50;
ObjectHeight = 50;
Rotation = 0.0;
FlipX = 0;
FlipY = 0;
BodyBehavior = Box2dBodyBehavior;
ShapeBehavior = Box2dShapeBehavior;
};
makeLevelObject(%myParent, %newLevelObj.CloneObjectName, %newLevelObj.PositionX, %newLevelObj.PositionY, %newLevelObj.ObjectWidth, %newLevelObj.ObjectHeight, %newLevelObj.Rotation, %newLevelObj.FlipX, %newLevelObj.FlipY, %newLevelObj.BodyBehavior, %newLevelObj.ShapeBehavior);
#59
07/22/2009 (9:30 am)
makeLevelObject() creates a whole new BehaviorInstance near the end of the function and does not modify it. That means this instance will have the default values.
The best way to modify the above code is to use the cloneWithBehaviors() method:
// Set up the prototype object for cloning:
%prototype = new t2dAnimatedSprite()
{
    name = "Prototype1";
};

// Configure the BehaviorInstance as you need it. This
// instance will be cloned later.
%bodyBehaviorInstance = Box2dBodyBehavior.createInstance();
%bodyBehaviorInstance.LinearDamping = 10;
%bodyBehaviorInstance.IsBullet = true;

%prototype.addBehavior( %bodyBehaviorInstance );

%newLevelObj = new ScriptObject() {
    CloneObjectName = "Prototype1";
    PositionX = 0;
    PositionY = 0;
    ObjectWidth = 50;
    ObjectHeight = 50;
    Rotation = 0.0;
    FlipX = 0;
    FlipY = 0;
};


function makeLevelObject( %myParent, %CloneObjectName, %PositionX, %PositionY, %ObjectWidth, %ObjectHeight, %Rotation, %FlipX, %FlipY, %BodyBehavior, %ShapeBehavior )
{
    %LevelObj = %CloneObjectName.cloneWithBehaviors();
    $LastObjectSelected = %LevelObj;

    %LevelObj.setPosition(%PositionX SPC %PositionY);
    %LevelObj.myParent = %myParent;
    %LevelObj.setSize(%ObjectWidth, %ObjectHeight);
    %LevelObj.setVisible(true);
    %LevelObj.useMouseEvts = true;
    %LevelObj.Rotation = %Rotation;
    %LevelObj.setRotation(%LevelObj.Rotation);
    %LevelObj.setFlip(%FlipX, %FlipY);

    // This is not needed any more as the behavior instance
    // with settings from above has been cloned in the first
    // statement of the function
    //%BodyInstance = %BodyBehavior.createInstance();
    //%LevelObj.addBehavior(%BodyInstance);
}

#60
07/22/2009 (11:32 pm)
Thank you. I implemented your suggestion as follows....

The following function is called in function startGame(%level):
function addImage()
{
   %object = new t2dStaticSprite()
   {
     ProtoType = "Triangle";   
   };
   
   %bodyBehaviorInstance = Box2dBodyBehavior.createInstance();  
   %bodyBehaviorInstance.LinearDamping = true;  
   %bodyBehaviorInstance.IsBullet = true;  
   
   %ShapeInstance = Box2dShapeBehavior.createInstance();
   %ShapeInstance.Density= 10.0;
   
   %object.addBehavior(%bodyBehaviorInstance);  
   %object.addBehavior(%ShapeInstance);
  
   %newLevelObj = new ScriptObject() {
      CloneObjectName = %object.ProtoType;//%name;
      PositionX = 0;
      PositionY = 0;
      ObjectWidth = 50;
      ObjectHeight = 50;
      Rotation = 0.0;
      FlipX = 0;
      FlipY = 0;
   };
   
   cloneObject(%newLevelObj.CloneObjectName, %newLevelObj.PositionX, %newLevelObj.PositionY, %newLevelObj.ObjectWidth, %newLevelObj.ObjectHeight, %newLevelObj.Rotation, %newLevelObj.FlipX, %newLevelObj.FlipY);
}

function cloneObject(%CloneObjectName, %PositionX, %PositionY, %ObjectWidth, %ObjectHeight, %Rotation, %FlipX, %FlipY, %BodyBehavior, %ShapeBehavior)
{
   %object = %CloneObjectName.cloneWithBehaviors();
   %object.setPosition(%PositionX SPC %PositionY);
   %object.setSize(%ObjectWidth SPC %ObjectHeight);
   %object.setRotation(%Rotation);
   %object.setFlip(%FlipX, %FlipY);
}

The Object is cloned but it does not get any type of behavior on it.
Can you please tell me what I am doing wrong?