Home Labs Galleries PetitOn

Using the JigLibFlash Physics Engine, Part 2

Jumping balls

Let's get back to the falling balls from the myth buster section above. As you probably noticed the balls didn't really bounce as we would expect a ball to behave. It was more like a heavy lump hitting the ground, and almost immediately stop its motion. I would really like them to bounce a little higher and a few more times.

This is a good physics engine, and we can easily change that by altering a property of one or both of the bodies that collide. In this respect there is no difference between the ground and a ball. They are both rigid bodies to the physics engine. The property to change is restitution. So let's start by changing the restitution of the ground.

In the createScene method it will look like this:

   var ground:RigidBody = physics.createGround("ground", appearance, 200, -100);
   ground.restitution = 3;

[This is how it looks] - get the source

Now they behave more like real balls. We will talk about why in just a moment, but first we have to note that restitution properties are mutual, i.e. as two bodies collide, there restitutions are added. So why not change the restitution of one of the balls, say the heavier red ball and see what happens.

   sphere2.mass = 5;
   sphere2.restitution = 0.3;
   var ground:RigidBody = physics.createGround("ground", appearance, 200, -100);
   ground.restitution = 3;

[JupingBalls2] source

I have only changed the restitution of one ball to 0.3, but the effect is really fine. Teh heavier ball is now more elastic, maybe harder pumped. By the way, the default value of the restitution is 0.2, so you see we have to be careful, when we set this elasticity value. Too high values, and the ball will jump to the moon and continue for ever ( or until we shut down the computer ).

Some physics

Let's have a bit of physics! Don't shy away - no math I promise. If you are a physics habitué, you can jump this session. If not, you may well benefit from this. Note that the following description is a simplification and as such far from complete.

So, what's the difference between the dead lump and the joyfully bouncing ball?

Let's start with the energy. According to an important law of physics energy can not be destroyed, ever. Before a ball starts to fall it has no real energy it seems. But for a physicist it has what is called potential energy, which is the energy in relation to the world, the energy that it would get, should it start to fall. Complicated? Not really.

When our ball starts to fall under the influence of gravity, it doesn't fall with constant speed. It accelerates, and when gaining more speed, its energy grows. This is called kinetic energy and depends on the velocity and the mass of the body. When the ball hits the ground it has a certain amount of kinetic energy. The potential energy it had in relation to the ground is now converted into kinetic energy. If that energy cannot be destroyed, it has to go somewhere. But where?

Let me rephrase the law. Energy can not be destroyed, it can only be transformed into other forms of energy. Ultimately all kinetic energy of our ball will be transformed into heat, the lowest form of energy. But before that, some kinetic energy may be reused

This is where the difference between the lump and the ball comes in. How that energy is used has to do with a pair of properties: elasticity and plasticity. If the ball is purely plastic, it will hit the ground and stop dead, like a piece of clay or a wet towel. All its kinetic energy will turn into heat in the ground and in the lump itself.

If, on the other hand, it is absolutely elastic, it will not give up any kinetic energy at all, and will continue bouncing for ever. Our natural ball will have a certain amount of elasticity, that will make it jump off the ground, yielding only some of its energy to heat. At the next hit, some more will become heat. At each bounce it will loose more of its kinetic energy and the height it reaches will decrease. Finally it stops bouncing, as all kinetic energy has been consumed to heat the ball and the ground.

There is certainly more questions to discuss, as for example how the ball yields its energy to heat and how it knows to use the remaining energy to jump, but I think we cannot take this anymore. So let's get back to the cozy world of programming, at least for now :)

Manual binding

As we have seen, the Sandy visual shape has to be bound to an JigLib rigid body. this can be done in one of two ways. Either automatically, using the convenience methods of the adapter class, or explicitly. So far we have used the automatic approach, but sometimes we may want to manually add visual objects to the physics engine, for example when we import ready made objects, using a parser.

Let's go back to the Hello JigLib example and modify that to use an explicit binding.
Here is a recap of the createSphere method we used.

private function createSphere():void {
   var sphere:RigidBody = physics.createSphere("sphere", null, 50, 7, 5);
}

Behind the scenes, both the Sandy Sphere and the JigLib JSPhere are created and tied together. The Sphere is added to the rootNode and the JSPhere to the physics engine.

Now we will take care of all creation and bindings ourselves. If you take a peek inside the Sand3DPhysics adapter, you'll se how this can be done.

Here is our new createSphere method.

private function createSphere():void
{
   var sphere:Shape3D = new Sphere("sphere",20,7,5);
   rootNode.addChild(sphere);

  // Now bind this Sandy object to a Jsphere
  var jsphere:JSphere = new JSphere(new Sandy3DMesh(sphere), 20);
  physics.addBody(jsphere);
}

First we create a sphere as we usually would in a Sandy application and add it to the root node. Then we create the JSphere counterpart with the same dimensions, passing the Sandy3DMesh to the constructor. Finally we add this RigidBody to the physics engine.

Now we have decoupled the creation of the visual object from that of the physical. We can now use any 3D object and give it physical properties. The catch here is that we have to find a a JigLib object that approximates the form of the visual object as closely as possible, to get correct behavior at collisions.

[It looks like the hello app] the source

Falling debris

Let's try something simple again, like falling primitive objects. We will use the explicit binding, to make clear how the visual and physical parts are created and bound. At the same time I'll introduce some new aspects on the workings of the physics engine.

Our createScene method for this experiment will be a bit longer, so let's discuss the different parts one at a time. We will need to import more classes from the Sandy and JigLib libraries, which you discover by reading the full code.

Here is the createScene method dissected. To bring some beauty to our objects, we start by creating some nice Materials. For details about appearances, materials and material attributes, see Sandy material tutorials.

private function createScene():void
{
   // Create some good materials
   var attributes:MaterialAttributes
          = new MaterialAttributes( new LightAttributes(true));
   var ballMaterial:ColorMaterial = new ColorMaterial(0xAA1200, 1, attributes);
   var ballAttributes2:MaterialAttributes
          = new MaterialAttributes(new PhongAttributes(true, 0.2, 15));
   var ballMaterial2:ColorMaterial
             = new ColorMaterial(0xCE24F9, 1, ballAttributes2);
   var cylMaterial:ColorMaterial = new ColorMaterial(0xE0B42F, 0.3, attributes);
   var boxMaterial:ColorMaterial = new ColorMaterial(0x10E465, 1, attributes);
   var boxMaterial2:ColorMaterial = new ColorMaterial(0xFFAA00, 1, attributes);
   var groundAttributes:MaterialAttributes
            = new MaterialAttributes(
                 new LineAttributes(0.5,0xECECEC,0.6), new LightAttributes(true));
   var groundMaterial:ColorMaterial
           = new ColorMaterial(0x26A6D0, 1, groundAttributes);

   // Make the materials accept lighting
   ballMaterial.lightingEnable = true;
   cylMaterial.lightingEnable = true;
   boxMaterial.lightingEnable = true;
   boxMaterial2.lightingEnable = true;
   ballMaterial.lightingEnable = true;
   ballMaterial2.lightingEnable = true;

With that out of the way, we start to create cylinders, boxes and spheres. They will be affected by the world gravity and fall until they hit the ground, so we'll give the start positions above the visible space. If we place them at different height, and spread them in x and set values they will hit the ground at different times and in different spots.

for ( var i:int; i < 10; i++) {
   var length:Number = randRange(20, 50);
   var cylinder:Shape3D = new Cylinder("cyl" + i, 10, length, 12, 4);
   cylinder.appearance = new Appearance(ballMaterial);
   rootNode.addChild(cylinder);

   // Now bind this Sandy object to a JCapsule
   var capsule:JCapsule = new JCapsule( new Sandy3DMesh(cylinder), 10,length);
   capsule.y = randRange(250, 550);
   capsule.x = randRange(0, 200) - 100;
   capsule.z = randRange(0, 200) - 100;
   capsule.rotationZ = randRange(0, 120);
   capsule.mass = randRange(1, 5);
   capsule.friction = 0.4;
   physics.addBody(capsule);
}

Here we create 10 Sandy Cylinder's with radius 10 and heights between 20 and 50. To get variation, we use a utility method randRange, defined in the same class. We dress each cylinder in the ballMaterial and add it to the root node. Here we are done with the Sandy part.

Next we create a JigLib JCaspul, which is the Cylinder counterpart and give it the same size. By passing the Cylinder object to the constructor, we bind the two objects together. We want to give the cylinders different starting positions, so we set random values for x, y and z. Instead of starting our objects from different heights, we could have used timer events to create them at different times. I experimented a bit with the y positions to make it look good.

We also give each cylinder a slight static rotation.  To make the objects behave naturally, we can change parameters like mass, friction and restitution. This requires quit a lot of experimentation and fine tuning, to get the desired effects.

Finally, don't forget to add the object to the physics engine.

The boxes and spheres are created in the same manner.

for ( var j:int; j < 10; j++) {
   var boxLength:Number = randRange(20, 40);
   var box:Shape3D = new Box("box"+j, 15, boxLength, 15,"quad", 2);
   box.appearance = (j % 2 == 0? new Appearance(boxMaterial):
                                 new Appearance(boxMaterial2));
   box.useSingleContainer = false;
   rootNode.addChild(box);

   // Now bind this Sandy object to a JBox
   var jbox:JBox = new JBox( new Sandy3DMesh(box), 10, 10,length);
   jbox.y = randRange(150, 600);
   jbox.x = randRange(0, 150) - 50;
   jbox.rotationZ = randRange(0, 120);
   jbox.rotationX = randRange(0, 120);
   jbox.mass = randRange(1, 5);
   jbox.friction = 0.6;
   physics.addBody(jbox);
}
for ( var k:int; k < 5; k++) {
   var sphere:Shape3D = new GeodesicSphere("sphere"+k, 15,5);
   sphere.appearance = (new Appearance(ballMaterial2));
   rootNode.addChild(sphere);

   // Now bind this Sandy object to a JSphere
   var jsphere:JSphere = new JSphere( new Sandy3DMesh(sphere), 15);
   jsphere.y = randRange(450, 1200);
   jsphere.x = randRange(0, 150) - 50;
   jsphere.mass = randRange(3, 6);
   jsphere.restitution = 0.8;
   jsphere.friction = 0.6;
   physics.addBody(jsphere);
}

For the spheres, we can choose between the old Sphere and the GeodesicSphere. The latter will handle a bitmap better, and though we only use simple color materials here, we might want to change that later.

What remains to create is the ground or floor, for the objects to bounce off. I also kept the auto generated sphere from the falling ball example, just for the heck of it :)

   var sphere2:RigidBody = physics.createSphere( "sphere",
              new Appearance( ballMaterial ), 20, 10, 10);
   sphere2.x = -160;
   sphere2.mass = 5;
   sphere2.restitution = 2;

Here is the ground

   var ground:RigidBody = physics.createGround("ground",
              new Appearance(groundMaterial), 400, -50, 10);
   physics.getMesh(ground).enableForcedDepth = true;
   physics.getMesh(ground).forcedDepth = 4000;

   ground.yaw(0.05);
   ground.friction = 0.6;
   ground.restitution = 1;
} // End of createScene

I used the createGround method in the adapter class to generate the ground, so that's ready made, including the appearance a I passed. In Sandy, as in many other 3D engines we have a z sorting problem, whith the result that hidden surfaces sometimes is drawn above visible ones. Objects may blend in with the ground, which looks strange. To limit this artifact, you can force the ground to be draw at a certain depth.

We need access to the Sandy object to set its depth value, which we get by calling the getMesh method on the adapter, passing a reference to the JigLib object.

We give the ground a small inclination, to make the bouncing more interesting. You can do that, using roll, pitch and yaw or any of the rotate methods.

[Here is FallingDebris] with source

Fun, isn't it? :D

We can see that the ground plane is just that, it is infinite, and the objects never falls off that plane. The size we give it is just for the visual part, which is a Sandy Plane3D.
If we wanted it to be limited, we would use a Box/JBox pair for the ground.

As we can see, some of the capsules never come to rest. That can be changed by altering damping or other parameters. We'll get to that later.

A test bench

There are lots of little things to test, before we can start any full blown project with this library, so I thought we should create a test bench, or rather a test room for further investigations. It will contain the basic ground and four walls, left, right back and front. In an enhanced version, we should have the possibility to set up walls as we need them and have mouse and keyboard controls for, but for now we can add interactivity as needed.

In earlier examples we had a createScene method to populate the scene, called from the constructor. Now we'll have a createRoom method for rising the walls, and a createObjects method for our experiment. The first will  never change, except that we may take down a wall temporarily.

Let's build the room. We have the same floor as before, although we don't slant it.

private function createRoom():void
{
   // Create some good materials
   var ground:RigidBody = physics.createGround("ground",
             new Appearance(groundMaterial), 800, 0, 10);

   physics.getMesh(ground).enableForcedDepth = true;
   physics.getMesh(ground).forcedDepth = 4000;
   ground.friction = 0.6;
   ground.restitution = 1;

I won't go in to the material business here, but you can check them out in the source.
We can let the adapter class create the ground for us, and set up the size, level and quality. The result as you know is a ground plane comprised of a Plane3D and a JPlane.

The forcedDepth trick is to avoid that objects in the room mix in with the walls. It is a well known work around for a well known problem - the z sorting fault.

Here is one of the walls, created by the adapter class as a Box - JBox pair.

   var leftWall:RigidBody = physics.createCube("left",
                new Appearance( wallMaterial), 2, 400, 400, 3 );
   leftWall.movable = false;
   leftWall.x = -300;
   leftWall.y = 200;
   physics.getMesh(leftWall).forcedDepth = 40000;
   physics.getMesh(leftWall).useSingleContainer=false;

This wall will be 400 by 400 with a thickness of 2. The thin box is created with its center in the origin, so we have to move it up and to the left, to get a left wall. We force the display depth, again to avoid to much blending between objects and the wall, when the get close. Note that floor and walls should be forced to different depths.

The other walls are created in the same manner. You might wonder why I use boxes for the walls. Well, it looks nicer, but the main reason is, that I had no luck using planes. It resulted in all kinds of wired behavior. I didn't get the collisions I wanted, and it looked like I got horizontal gravity from the front wall. Some day I'll have an explanation :)

The createObjects method, only contains our old sphere from the falling debris experiment, not to leave the room empty. I have lifted the ball up a bit, to give it some energy for bouncing.

[The ball room] with source

A first test bench experiment

To be a little more organized, I recommend that we follow the good example of the adapter class and BasicView developers. Instead of editing the TestBench for each experiment, saving it under a new name, we can just inherit the TestBench class, and implement the createObjects method.

This makes it a lot easier, as get short code to handle, and no need to sift through loads of unchanged code, to find the few lines we are working on. We'll find some other way to tear down one or more of the walls, if needed.

The new TestBench class has a front wall added, and requires a stage size of 600 by 400 pixels. The walls are semi transparent, so we can see what's going on outside the box.  The transparency of the front wall makes it just barely visible. The test bench needs further development, for example to optionally raise and tear down walls, and to set a roof on demand.

Here is our whole BounceBall class, after we move the createObjects method.

package
{
  import sandy.materials.attributes.LightAttributes;
  import sandy.materials.attributes.LineAttributes;
  import sandy.materials.attributes.PhongAttributes;
  import sandy.materials.attributes.MaterialAttributes;
  import sandy.materials.attributes.GouraudAttributes;
  import sandy.materials.ColorMaterial;
  import sandy.materials.Appearance;

  import jiglib.physics.RigidBody;

  public class BounceBall extends TestBench
  {
     // Set up the test bench and create the experiment
     public function BounceBall(){
        super();
        createObjects();
     }

     private function createObjects():void {
        var attributes:MaterialAttributes =
                      new MaterialAttributes( new LightAttributes(true));
        var ballMaterial:ColorMaterial = new ColorMaterial(0xAA1200, 1, attributes);
        ballMaterial.lightingEnable = true;
        var sphere2:RigidBody = physics.createSphere("sphere",
                                 new Appearance( ballMaterial ), 20, 10, 10);
        sphere2.x = -100;
        sphere2.y = 300;
        sphere2.z = -100;
        sphere2.mass = 5;
        sphere2.restitution = 1;
     }
   }
}

When we start our experiments, we will just work in the createObjects method, even if we might have to add some imports and instance variables as we go.

[The new testing room], sources: TestBench and BounceBall

More examples to come shortly on how to apply constraints on objects, i.e. to bind objects together to work in concert and how to apply forces and momentum.

I have a lot to learn, and so do you :)

---------------------- Next Page ---------- Discussion + Q&A - to be cont'd ...

Conceptual views

You can look at the relation between the visual Sandy world and the physics world of JigbLib from different perspectives.

If you have worked with the Sandy 3D world, you are used to creating visual objects, placing them on the scene and possibly adding some frame based animation to the objects or the camera. There is no physics involved, unless you calculate new positions for the objects in a frame based or timer based mode. This is exactly what the Jiglib physics engine does. You will probably look at the physics engine as a way of positioning your objects according to the laws of physics. The adapter classes for your specific 3D rendering engine, in our case Sandy 3D, are just interfaces for getting the math of physics applied to your objects.

If, on the other hand, if you are the JigLib guy, you look at your physics engine as a way of describing how rigid bodies move and collide under the influence of forces. This would be your main interest, and you only need a 3D rendering engine to visualize the behavior of the bodies. You tend to look at the adapter classes as a visual rendering plugin for the physics engine.

The adapter classes Sandy3DPhysics and Sandy3DMesh simply comprise the interface, as a tool for communication between the two worlds.

Jiglib is a pure mathematical world with no visual representation. It calculates the behavior of rigid bodies under the influence of forces acting upon them, such as gravity, springs and rigid constraints. These bodies move and collide, attract and repel each other according to the laws of physics.

Sandy 3D is a visual world, more concerned with the shape and looks of objects. It creates objects with no physical properties, except for position in a 3D world and visual ones as color and light reflection.

The interface between the two worlds allows for visual objects in all there elegance to behave like real objects with mass in a 3D world with forces acting upon them. This certainly makes them more convincing than ...

For the visitor of this combined world, the duality is is not visible, which is the whole point. He sees objects falling, jumping and colliding in a believable fashion ( rephrase ).

For the application developer, it is certainly another story, whether he is the physics guy, who wants to see his math illustrated, by rendering visual objects or the game developer who wants his colorful creations behave in a truly physical manner.

Objects in the two worlds would live and behave independently if we didn't connect them via the interface or adapter classes. The Sphere primitive in the Sandy world has its counterpart JSphere in the Jiglib world. By connecting the two objects, the Sphere will  behave like a physical object and the JSphere will get its visual representation on screen.

In essence the two objects look and behave as one object in one physical, visual 3D  world. This is a really elegant solution to the problem of visualizing a physical world.

So what's the catch for the application programmer?

Well, the tools are there, but we have to set up the visuals and the physics in such a way that the end result is believable. Gravity and other forces must be given correct values. Collisions have to be detected and bodies should react to collisions and other constraints, such as joints and string connections between bodies in a correct fashion.

I'll try to learn and give advice for how this can be done....