Click here to Skip to main content
Click here to Skip to main content

Tagged as

Controlling Drawn Shapes #2

, 22 Nov 2013 CPOL
Rate this:
Please Sign up or sign in to vote.
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 article, I explain these improvements and some of the caveats I ran into. The article 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.

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.

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:

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:

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.

[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 include 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:

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)

Share

About the Author

rld1971
Team Leader Formulatrix
United States United States
No Biography provided

Comments and Discussions

 
GeneralEager to hear some recommendations and questions! Pinmemberrld19718-May-14 13:57 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.141216.1 | Last Updated 22 Nov 2013
Article Copyright 2013 by rld1971
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid