Click here to Skip to main content
15,879,095 members
Articles / Programming Languages / C#
Tip/Trick

Controlling Drawn Shapes #2 (simulator)

Rate me:
Please Sign up or sign in to vote.
4.75/5 (6 votes)
15 Jan 2015CPOL7 min read 36.8K   1.1K   26   7
Adding a physics engine and investigating other .NET tools

Introduction

A couple of years after my first posting, I am releasing an upgrade where I did the following:

  • Changes and cleanups to the original article to improve ease of use (as a coder and as a user)
  • Added a physics engine (gravity, momentum, and drag)
  • Bound objects to a grid to improve debugging
  • Added a TypeConverter

In this tip, I explain these improvements and some of the caveats I ran into. The tip itself is not a recipe of how to recreate this, for that, you can read the code. Nothing here is so complicated that it cannot be followed.

When you run this, you will be able to grab many objects in a flat world and throw them around. You'll see how gravity, mass, friction all interact with each other. As part of this, you will learn how to model all this yourself.

Using the Code

The Basics

Similar to last time, I rely on an interface called IShape. However, I broke it up a little into IDrawable and IShape. An IShape is an IDrawable. The basic difference is that an IDrawable is something that has no independent interactivity within a canvas, whereas an IShape does.

Shapes

Shapes have evolved very little since the last article. The biggest change I made is that I made more of an OO hierarchy to pull out more common code to the base class Shape and be able to better reuse it within the concrete shapes, such as selection and drawing. I guess this pretty much follows a template pattern. I also added some fields that allow it to interact with the physics engine described below.

The Canvas

This is the main drawing area, it is very similar as in the previous article, but I'll explain some concepts that I changed. I added a field of view (fov) which defines the location and size of the view. As opposed to the original article where you had to define a zoom and angle, now I define a field of view (http://en.wikipedia.org/wiki/Field_of_view) which makes it much easier to define where the canvas is located and what its size is, as shown below. In my application, most units are in terms of metres.

C#
zoomPanel.Initialize(universe, new RectangleF(0, 0, 10, 10));
... 

largePanel.Initialize(universe, new RectangleF(-50, 0, 110, 55));
... 

Here, the zoom panel has a size of 10x10m whilst the large panel measures 110, 55 and includes the zoom panel somewhat centered. The canvas takes its own set of universe which means that they could show you different things, not sure how realistic that is. I removed the rotation aspect because I wanted to deal more with gravity (later) so down should always be down.

The canvas can be dragged around using the right button. The canvas is an IDrawable since I want to make sure it gets drawn to let the user where it is located. It's not an IShape since it does not interact with Shapes.

Debug Panels

I had previously added a little log area at the bottom here. Nothing fancy, just a text area that you can easily get to. However, my app was becoming too verbose so I decided to display position information of each object on a grid. Of course, I wanted to learn a little more, so after a little searching, I ended up binding the list of shapes directly to the grid which enabled me to do more fancy stuff like adding interactivity and of course "auto-update" as the object changes.

Binding the universe to the grid was as simple as this:

C#
private void BindShapesToGrid(ShapeGroup universe)
{
    BindingSource bs = new BindingSource();  
    bs.DataSource = new BindingList<IShape>(universe.Shapes);
    dgModel.DataSource = bs;
} 

Fields that have a "setter" defined on them also become automatically settable through the grid, so I can easily add a specific height or velocity to any object without having to estimate using mouse movements.

I actually spent a lot of time here even though it was clearly taught here. At the end, it turned out that I had to make IShape to inherit INotifyPropertyChanged as opposed to Shape (which actually contains the PropertyChangedEventHandler event). I spent several hours because I also assumed that the .NET classes would be looking at the actual objects that were passed for the event, as opposed to the declaration of the type of the BindingList. Kind of an embarrassment, I guess it won't happen again. Notifying changes were easily done as advertised:

C#
private void NotifyPropertyChanged(string name)
{
    if (PropertyChanged != null)
        PropertyChanged(this, new PropertyChangedEventArgs(name));
}  

Another cool thing that I learned here was the TypeConverter framework. I wanted to enter new velocities to any object through the grid. In order to parse the strings and convert them to Vector3, I added Vector3Converter, this is all well explained in the Microsoft documentation, but here are the basics. The class that needs to be converted gets tagged with an attribute that tells it which class provides the conversion.

C#
[Serializable]
[TypeConverter(typeof(Vector3Converter))]
public struct Vector3... 

Then, if you look at the class definition [of Vector3Converter], it implements TypeConverter and overrides some simple methods. Take a look at the code for the details.

Physics Engine

Finally, we get to the physics engine. I wanted to be able to:

  • Throw things around
  • Have things fall to the ground
  • Have an atmosphere that exerts some level of drag

There are two basic parts to this part: a modeler that figures out how forces act on an object, and a simulator that simulates the passage of time. Given forces and masses, it is possible to deterministically figure out how far things move during an amount of time (delta T, or elapsed time). The passage of time needs to be simulated.

Time Simulator

This is actually so simple that is is simply Timer with an interval set at 10ms. That means that ever 10ms or so, an event is fired which executes the physics modeler. As you can see, I said 10ms "or so", which at this resolution is usually more "or so" than not. To get things really accurate, the simulator needs to keep its own time and use that as the actual time elapsed between two events. For this, I used a StopWatch which easily tells me the actual time between two events. I was happy when I was able to simulate an object falling from 1000m (assuming a drag of 0) and have the time match what the math would say. If you don't have your own way of keeping time, your objects will fall too slow.

I think I'll create a simple class that includes the Timer and StopWatch to implement the simulator on my next iteration.

Physics Modeler

First some physics basics - not that I remember much from school, I used Wikipedia quite a bit to learn some equations, especially regarding drag.

  • a (acceleration) =gravity = 9.8 m/s^2
  • v (velocity) = u + at (t = time elapsed, u = start velocity, this is really the integral of a)
  • s (displacement) = ut + 0.5at^2
  • FDrag (Drag Force) - See http://en.wikipedia.org/wiki/Drag_equation
  • f = ma (this enables me to account for FDrag when applying gravity)

I am not overly confident with my application of drag (and you can see that by the divisor of 500. However, the basics seem to work. A bigger object will fall slower due to its increased area dragging. Again though, I took a simplification and assumed that everything weighs 1 kg. I'll probably change this in a future article to play with other concepts. If you can tell me what the proper way to apply drag is, let me know!

Once you are comfortable with the equations, you just plug and play. It's important to remember that forces and velocities are vectors. I suppose it is possible to create a single vector that describes net forces from the environment, but instead I decided to work on the components of these vectors individually. I'm not so advanced...

The other part when working with floats or doubles is to check values with some level of allowable error, and in my case, I also wanted to have pieces stop moving otherwise they would continue bouncing quite a bit which was just annoying. For that, I added this simple code:

C#
if (Utilities.CheckWithAbsTolerance((float)vx, 0f, 0.1f)) vx = 0f;
if (Utilities.CheckWithAbsTolerance((float)vy, 0f, 0.1f)) vy = 0f; 

That's it! Try the code and play with it... let me know what you think!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Team Leader Formulatrix
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionTeacher defies logic Pin
AORD16-Jan-15 8:14
AORD16-Jan-15 8:14 
AnswerRe: Teacher defies logic Pin
rld19716-May-15 1:03
rld19716-May-15 1:03 
QuestionOnce I figured out the mouse is used to throw multiple objects around it became interesting Pin
AORD16-Jan-15 7:47
AORD16-Jan-15 7:47 
QuestionI like this work Pin
Member 472201516-Jan-15 6:14
Member 472201516-Jan-15 6:14 
GeneralEager to hear some recommendations and questions! Pin
rld19718-May-14 12:57
rld19718-May-14 12:57 
GeneralRe: Eager to hear some recommendations and questions! Pin
Southmountain8-Jun-16 15:48
Southmountain8-Jun-16 15:48 
GeneralRe: Eager to hear some recommendations and questions! Pin
bw28919-Mar-17 7:47
bw28919-Mar-17 7:47 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.