Tuesday, January 17, 2012

If You Can Name It, It's An Object

This is a continuation of a previous post on creating better design.

What happens when your class becomes a giant ball of garbage and you have no idea what anything does anymore. I throw it away and start again. Let's consider a Physics World object. What does it do? What is the role of this object? Let's start from the simplest example to the more difficult. I am going to ignore the double dispatch problem for now and completely ignore the issues regarding strong types in C++, but I will be taking advantage of that behavior.

Let's go back to the old example of my simple graphics app.

void main()
{
   System system(640, 480);
   Sprite ball("Ball.PNG");
   ball.velocity(1.0f, 1.0f);
   ball.position(10.0f, 20.0f);
   PhysicsWorld world();
   Scene scene();
   world.Add(ball);
   scene.Add(ball);
   Boundary bounds(0,0,640,480);
   world.Add(bounds);

   Timer timer;
   Keyboard keys;
   Mouse mouse;

   while(!keys.AnyKey() && !mouse.RightButton())
   {
      world.update(timer);
      scene.update(timer);
      Renderer renderer;
      renderer.Render(scene);
   }
}

Let's take out everything except what relates to the Physics World object.
void main()
{
   Thing ball();
   ball.velocity(1.0f, 1.0f);
   ball.position(10.0f, 20.0f);
   PhysicsWorld world();
   world.Add(ball);
   Boundary bounds(0,0,640,480);
   world.Add(bounds);

   Timer timer;
   Keyboard keys;
   Mouse mouse;

   while(!keys.AnyKey() && !mouse.RightButton())
   {
      world.update(timer);
   }
}

Since this is not a post about how physics engines work, I'm going to ignore a lot of details. The details I'll be omitting is not relevant to this discussion, but is Critical to a proper Physics Engine!

OK. First I have changed the object from Sprite to Thing. I'll be writing about this object family in another post, but it's pretty complex and involves a lot of template meta programming. For our purposes what matters is that it has a physical body. It has physical properties like velocity and position. It has something that can be collided with, and since it moves, it can collide. That's an important concept when we consider efficiency.

Next we create a world. Really not much to say about that. Then we add the ball to the world. We're using overloaded functions so we only have the function Add(). This makes it easier to use the object because there is less to memorize. If you think you can add it to the physics world, you should be able to just Add() it. You don't have to memorize what kind of add to use. In the case of my real code the Add() is more complicated because the objects being used are collections of policy classes and the user may not know what the actual type is with regards to the Physics World object. This brings up a couple of important points.

Make your objects easy to use right and hard to use wrong.


Creating multiple Add functions is not as easy as having everything in physics derive from a common base class, but this adds an unnecessary burden to the user of your code by requiring a tight binding that may be undesirable or impossible, and makes the add function hopelessly cluttered with conditionals.

An objects type is as valid a piece of data as anything else.


The fact that I have a Dynamic Body, information that is hidden in this example, means that I know extra processing will have to be done that is unnecessary if the object is a Static Body. While I could have an if statement or a switch statement add objects to the correct container, why not let the compiler do it? This creates two clean, simple functions rather than one god awful mess. This will become more obvious as we continue.

When we add bounds, we are doing the same thing except this time bounds is a Static Body. When we get to the update function we don't have to update the position of bounds.

OK, but what if we want to have gravity?

Too often Physics worlds include gravity as part of the world object. This makes physics worlds very complex. It's not just gravity they include, but also ground planes and up vectors, and all kinds of other crap. So how do we get the same complex behavior in a simplified system?

Let's create a Gravity Object.

void main()
{
   Thing ball();
   ball.velocity(1.0f, 1.0f);
   ball.position(10.0f, 20.0f);
   COR cor(0.7f);
   ball.Add(cor);

   PhysicsWorld world();
   world.Add(ball);
   Boundary bounds(0,0,640,480);
   world.Add(bounds);

   // Default constructor assumes gravity is 1.0f in the -Y direction. 
   Gravity gravity;
   world.Add(gravity);


   Timer timer;
   Keyboard keys;
   Mouse mouse;

   while(!keys.AnyKey() && !mouse.RightButton())
   {
      world.update(timer);
   }
}

OK. We created Gravity and added it to the world. That's pretty simple. We could have changed properties if we wanted different behavior. What the heck is that COR? It's the Coefficient Of Restitution. It makes the ball not perfectly bouncy. I actually do this a different way in my code, but without getting into how Generic Programming works it's easier to think of it this way. And if I didn't have other tools available it's how I would handle complex behaviors of physics bodies.

Let's change the code completely and have the ball floating in space with a giant planet.

void main()
{
   Thing ball();
   ball.velocity(1.0f, 1.0f);
   ball.position(10.0f, 20.0f);
   COR cor(0.7f);
   ball.Add(cor);

   PhysicsWorld world();
   world.Add(ball);
   Boundary bounds(0,0,640,480);
   world.Add(bounds);

   // Default constructor assumes gravity is 1.0f in the -Y direction. 
   PointGravity gravity(150.0f, 150.0f);
   world.Add(gravity);


   Timer timer;
   Keyboard keys;
   Mouse mouse;

   while(!keys.AnyKey() && !mouse.RightButton())
   {
      world.update(timer);
   }
}

That's all there is to it. How about we make an atmosphere to slow us down?

void main()
{
   Thing ball();
   ball.velocity(1.0f, 1.0f);
   ball.position(10.0f, 20.0f);
   COR cor(0.7f);
   ball.Add(cor);
   COD cod(0.8f);
   ball.Add(cod);

   PhysicsWorld world();
   world.Add(ball);
   Boundary bounds(0,0,640,480);
   world.Add(bounds);

   // Default constructor assumes gravity is 1.0f in the -Y direction. 
   PointGravity gravity(150.0f, 150.0f);
   world.Add(gravity);

   PointAtmosphere atmos(150.0f, 150.0f, 15.0f);
   world.Add(atmos);


   Timer timer;
   Keyboard keys;
   Mouse mouse;

   while(!keys.AnyKey() && !mouse.RightButton())
   {
      world.update(timer);
   }
}

We add a drag coefficient to the ball and add an atmosphere to the gravity source. Well, that's OK, but this is for my physics class and that really doesn't cut it.

How about this?

void main()
{
   Thing ball();
   ball.velocity(1.0f, 1.0f);
   ball.position(10.0f, 20.0f);
   COR cor(0.7f);
   ball.Add(cor);
   COD cod(0.8f);
   ball.Add(cod);

   PhysicsWorld world();
   world.Add(ball);

   Planet earth(1.0f, 6,371.0f);
   Atmosphere atmos(1.0f);
   earth.Add(atmos);
   world.Add(earth);

   Timer timer;
   Keyboard keys;
   Mouse mouse;

   while(!keys.AnyKey() && !mouse.RightButton())
   {
      world.update(timer);
   }
}

You could continue and make the whole solar system and if you get the data correct, you will have a stable system, or at least as stable as the real one is.

Notice that I add a planet, but do not extract the gravity from it and add that as an additional step. Why should I? The function is customized for planets so why wouldn't the function do it? Also, I could add atmosphere to a region of space or to a planet. In either case the atmosphere is the same. I did use point instead of ambient, but the principle is the same. I could extend the concept of point atmosphere to add a hurricane to the earth.

We had a ball and rather than giving the ball properties, we instead created adverbs to append properties. So we had a "High drag bouncy ball." Rather than creating complex objects we created a noun phrase. "A planet with the same mass as earth and a radius of 6,371 km and one standard atmosphere." We could have added water and a molten core if we wanted.

I hope I made a reasonably clear case for not only creating a language to describe your problem, but also to use language constructs to build your language. 

2 comments:

  1. So in summary, "If it's not the whole, then it's a part. Make every part have its own object." :)

    ReplyDelete
    Replies
    1. Sort of. I'll be leading up to policy and traits classes which add adjectives and verbs, so to speak. I see a lot of bad code written with too strong a fixation on modeling the real world so I avoid that comparison. But yeah, that is one aspect of it.

      Delete