Search code examples
c#.netmultithreadingtimerstopwatch

Physics Project in C#: Problems with threading and measuring time


I'm having some problems with a physics project I'm trying to make. Its basically a Free Fall simulator. I set the gravitational acceleration and the height and the program should simulate the fall. The problem is that it's getting the time wrong, don't know why. I'm using the Stopwatch class for measuring the time.

Here's the code:

FrmMain.cs:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using System.Threading;
using System.Diagnostics;

namespace Physics
{
    public partial class FrmMain : Form
    {
        public Settings config = new Settings();

        public float displacement = 0f;

        public Stopwatch timer = new Stopwatch();
        public float timeElapsed; //in Seconds

        public Thread thread;

        public FrmMain()
        {
            InitializeComponent();

            //New thread (Drawing)
            thread = new Thread(new ThreadStart(Drawing));
            thread.IsBackground = true;
            thread.Start();

            this.KeyDown +=new KeyEventHandler(FrmMain_KeyDown);
        }

        private void Drawing()
        {
            try
            {
                while (true)
                {
                    if (config.objectPos.Y < config.sizeBitmap.Height)
                        timer.Start();
                    else
                        timer.Stop();
                    Bitmap screen = new Bitmap(this.pboxScreen.Width,
                                               this.pboxScreen.Height);
                    SendScreen(screen);
                }
            }
            catch (ThreadAbortException tae)
            {
                Console.WriteLine(tae.Message);
            }
        }

        private void SendScreen(Bitmap Screen)
        {
            if (pboxScreen.InvokeRequired)
            {
                pboxScreen.Invoke(new MethodInvoker(delegate()
                {
                    this.SendScreen(Screen);
                }));
            }
            else
            {   //Converting Milliseconds to Seconds
                timeElapsed = timer.ElapsedMilliseconds / 1000f;
                //Check if object isn't in the ground
                if (config.objectPos.Y < config.sizeBitmap.Height)
                 {
                    displacement -= config.objectPos.Y;
                    config.objectPos.Y = config.objectPos.Y + 0.5f * 
                                         config.acceleration * 
                                         timeElapsed * timeElapsed;
                    displacement += config.objectPos.Y;
                }

                Graphics g = Graphics.FromImage(Screen);
                g.Clear(Color.White);

                //New rectangle
                Rectangle rec = new Rectangle((int)config.objectPos.X, 
                                               (int)config.objectPos.Y, 5, 5);
                g.FillRectangle((new Pen(Color.DarkRed)).Brush, rec);
                g.DrawRectangle(new Pen(Color.Red, 2.0f), rec);

                g.Dispose();

                //Update txtbox (textbox)
                txtboxX.Text = config.objectPos.X.ToString();
                txtboxY.Text = config.objectPos.Y.ToString();
                txtboxTempo.Text = timeElapsed.ToString();
                txtboxD.Text = displacement.ToString();

                pboxScreen.Image = Screen;
            }
        }

        void FrmMain_KeyDown(object sender, KeyEventArgs e)
        {
            switch (e.KeyCode)
            {
                case Keys.Space:
                    if (config.objectPos.Y >= config.sizeBitmap.Height)
                    {
                        config.objectPos.Y -= 100;
                        timer.Reset();
                    }
                    break;
            }
        }

        private void FrmMain_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (thread.IsAlive) thread.Abort();
        }
    }
}

Settings.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;

namespace Physics
{
    public class Settings
    {
        public Size sizeBitmap; //bitmap resolution
        public PointF objectPos; //Initial Position
        public float acceleration;

        public Settings()
        {
            sizeBitmap.Width = 560;
            sizeBitmap.Height = 420;
            objectPos.X = 560f / 2f;
            objectPos.Y = 420f / 2f;
            acceleration = 9.8f;
        }

    }
}

There's another problem, depending on height the object doesn't fall without interruption, it stay in the same position for a few, but noticeable, milliseconds. I think this is a threading problem, 'cause it's been a while I don't use it, so I might be missing something.

I'm measuring the height in meters, so the objectPos.Y = 420f / 2f represent a height of 210 meters. For this height, the object should take around 6.5 seconds to hit the ground, in my program it's taking less than 1 second, so I assumed that there's a time measuring problem.

I'm calculating the height with the uniform gravitational field without air resistance expression :

h(t)=h0+0.5*g*t²

where: h(t) is the height with respect to time, h0 the initial altitude, g acceleration due to gravity and t the time elapsed

Any help is much appreciated. Thanks.


Solution

  • I believe your issue may be in the line

            config.objectPos.Y = config.objectPos.Y + 0.5f * 
                                 config.acceleration * 
                                 timeElapsed * timeElapsed;
    

    The YPosition at time t = t-sub-k (config.objectPos.Y) is the height at time t-sub-k. if t is total elapsed time, then this is equal to the Initial Y-Position (The Height at Time t = 0) + 1/2 gt^2, NOT the last Y-Position.

    What you are doing is taking the last Y-Position (the height as of the previous calculation, and adding the drop in altitude during the elapsed time from time t=0 to t= t-sub-k. So it's no wonder that it is falling too fast.

    Add an Initial Y-Position read-only property to your settings construct, and DO not Update or modify it.

    Modify your code to use it as follows:

            config.objectPos.Y = config.InitialY + 0.5f * 
                                 config.acceleration * 
                                 timeElapsed * timeElapsed;