Bouncing ball

ddanbe 1 Tallied Votes 2K Views Share

Wanted to let two forms work together. One form would be the input form and the main form would consume this data. I managed to succeed with the help of a few of my friends here at daniweb., who helped me setup a static form.
Just posted a bit of the most relevant code here, the rest of the project can be found in the attachments. I also like to mention a technique used here to attach a value to a selection of a name in a combobox.(Found it on MSDN) See the list of Planets. (Yes, throw a ball on different locations in the solar system!) I guess enough comments are provided to understand what is happening.
The final piece of code gets executed if you click the execute button.
In the BounceTrajectCalculation the position of the ball over time gets calculated. It makes heavy use of the data from the inputform.
The heart of the calculation(iteration) happens here:

t = t0 + InputData.timeIncr;                            //time
                    hDis = hDis0 + InputData.velocity * InputData.timeIncr; //length
                    Vel = Vel0 - InputData.g * InputData.timeIncr;          //velocity
                    h = h0 + 0.5 * (Vel + Vel0) * InputData.timeIncr;       //length

Those familiar with numerical integration will notice a method of Euler here. Hope the comments are sufficient to see what is happening. The code may be used as you see fit. Don't blame me if your tennis ball follows a different trajectory than predicted here;)

kvprajapati commented: Great! +6
namespace BouncingBallSimulation
{
    /// <summary>
    /// Class to store some data
    /// </summary>
    class Planet
    {
        private string myName;
        private double myGravitationalAcceleration;

        public Planet(string strName, double Acceleration)
        {
            this.myName = strName;
            this.myGravitationalAcceleration = Acceleration;
        }

        public string Name
        {
            get { return myName; }
        }

        public double GravitationalAcceleration
        {
            get { return myGravitationalAcceleration; }
        }
    }
}
/////////////////////////////////////////////////////////////////////////////////////
using System;
using System.Collections.Generic;
using System.Windows.Forms;

namespace BouncingBallSimulation
{
    /// <summary>
    /// ____________________________________________________________
    /// 
    /// Class to handle input of data needed in the main form class.
    /// It is implemented as static singleton.
    /// Thanks to Scott and adatapost for the hints on this.
    /// ____________________________________________________________
    /// </summary>
    public partial class InputData : Form
    {
        static InputData instance;

        static InputData() { instance = new InputData(); }
        
        public InputData()
        {
            InitializeComponent();
            // Handy way to couple a name in a combobox with a value
            List<Planet> Planets = new List<Planet>();
            Planets.Add(new Planet("Earth at equator 0°", 9.78));           
            Planets.Add(new Planet("Earth at equinox 23.5°", 9.788));
            Planets.Add(new Planet("Earth at lattitude 50°", 9.81));
            Planets.Add(new Planet("Earth at pole 90°", 9.83));
            Planets.Add(new Planet("Moon", 1.63));
            Planets.Add(new Planet("Mars", 3.69));
            Planets.Add(new Planet("Venus", 8.87));
            Planets.Add(new Planet("Titan", 1.352));
            // Add the list of celestial objects to our combobox
            GraviAccelCombo.DataSource = Planets;
            // Add names of properties from the Planet clas here, seems cool!
            GraviAccelCombo.DisplayMember = "Name";
            GraviAccelCombo.ValueMember = "GravitationalAcceleration";
            // Hard coded, I know but it is not likely these gravitation values will ever change.
        }
        
        //data with some initial values
        private static double _g = 9.81;
        private static double _height = 2.0;
        private static double _velocity = 1.2;
        private static double _bounceCoeff = 0.80;
        private static int _numBounces = 3;
        private static double _timeIncr = 0.05;

        public static double g // g=gravitational acceleration
        {
            get { return _g; }
            set { _g = value; }
        }

        public static double height 
        {
            get { return _height; }
            set { _height = value; }
        }

        public static double velocity
        {
            get { return _velocity; }         
            set { _velocity = value; }
        }

        public static double bounceCoeff
        {
            get { return _bounceCoeff; }
            set { _bounceCoeff = value; }
        }

        public static int numBounces
        {
            get { return _numBounces; }
            set { _numBounces = value; }
        }

        public static double timeIncr
        {
            get { return _timeIncr; }
            set { _timeIncr = value; }
        }

        public static void ShowForm() 
        { 
            if (instance.Visible)        
                instance.BringToFront(); 
            else        
                instance.Show(); 
        }

        private void GraviAccelCombo_SelectedValueChanged(object sender, EventArgs e)
        {
            if (GraviAccelCombo.SelectedIndex != -1)
            {
                g = ((Planet)GraviAccelCombo.SelectedItem).GravitationalAcceleration;
                AccelerationTxB.Text = GraviAccelCombo.SelectedValue.ToString();
            }
        }

        private void inputOKBtn_Click(object sender, EventArgs e)
        {
            //save data in private vars, do validation with errorProvider
            bool noErr = true;
            //  g   ////////////////////////////////////////////////
            try
            {
                _g = double.Parse(instance.AccelerationTxB.Text);
            }
            catch (FormatException)
            {
                noErr = false;
                instance.errorProvider1.SetError
                    (instance.AccelerationTxB, "You need to enter a real number.");
            }
            //  height  ///////////////////////////////////////////
            try
            {
                _height = double.Parse(instance.heightTxB.Text);
            }
            catch (FormatException)
            {
                noErr = false;
                instance.errorProvider1.SetError
                    (instance.heightTxB, "You need to enter a real number.");
            }
            //  velocity    ////////////////////////////////////////
            try
            {
                _velocity = double.Parse(instance.velocityTxB.Text);
            }
            catch (FormatException)
            {
                noErr = false;
                instance.errorProvider1.SetError
                    (instance.velocityTxB, "You need to enter a real number.");
            }
            //  bounce coefficient  ////////////////////////////////
            try
            {
                _bounceCoeff = double.Parse(instance.bouncecoeffTxB.Text);
                if ((_bounceCoeff < 0) || (_bounceCoeff > 1))
                {
                    noErr = false;
                    instance.errorProvider1.SetError
                    (instance.bouncecoeffTxB, "You need to enter a number between 0 and 1.");
                }
            }
            catch (FormatException)
            {
                noErr = false;
                instance.errorProvider1.SetError
                    (instance.numbounceTxB, "You need to enter a real number.");
            }
            //  number of bounces   ////////////////////////////////
            try
            {
                _numBounces = int.Parse(instance.numbounceTxB.Text);
                if (_numBounces < 0)
                {
                    noErr = false;
                    instance.errorProvider1.SetError
                    (instance.numbounceTxB, "You need to enter a number greater than 0.");
                }
            }
            catch (FormatException)
            {
                noErr = false;
                instance.errorProvider1.SetError
                    (instance.bouncecoeffTxB, "You need to enter a real number.");
            }
            // time increment   ////////////////////////////////////
            try
            {
                _timeIncr = double.Parse(instance.timeIncrTxB.Text);
            }
            catch (FormatException)
            {
                noErr = false;
                instance.errorProvider1.SetError
                    (instance.timeIncrTxB, "You need to enter a real number.");
            }

            if (noErr)
            {
                instance.errorProvider1.Clear();
                this.Hide();
            }
        }

        private void inputCancelBtn_Click(object sender, EventArgs e)
        {
            // Don't save any data, just hide
            this.Hide();
        }

        private void InputData_FormClosing(object sender, FormClosingEventArgs e)
        {
            e.Cancel = true; 
            this.Hide();
        }

        private void AccelerationTxB_VisibleChanged(object sender, EventArgs e)
        {
            AccelerationTxB.Text = _g.ToString();
        }

        private void heightTxB_VisibleChanged(object sender, EventArgs e)
        {
            heightTxB.Text = _height.ToString();
        }

        private void velocityTxB_VisibleChanged(object sender, EventArgs e)
        {
            velocityTxB.Text = _velocity.ToString();
        }

        private void bouncecoeffTxB_VisibleChanged(object sender, EventArgs e)
        {
            bouncecoeffTxB.Text = _bounceCoeff.ToString();
        }

        private void numbounceTxB_VisibleChanged(object sender, EventArgs e)
        {
            numbounceTxB.Text = _numBounces.ToString();
        }

        private void timeIncrTxB_VisibleChanged(object sender, EventArgs e)
        {
            timeIncrTxB.Text = _timeIncr.ToString();
        }

       
    }
}

//////////////////////////////////////////////////////////////////////////////////////
private void BounceTrajectCalculation()
        {
            // Calculate height of a ball at different time intervals
            double t, t0 = 0.0;         //time
            double hDis, hDis0 = 0.0;   //horizontal displacement
            double Vel, Vel0 = 0.0;     //velocity
            double h, h0 = InputData.height;  //vertical displacement           
            int BounceCount = 0;
            PlotValues.Clear(); // Clear the list of values to be plotted.
            using (StreamWriter swr = new StreamWriter("BounceDataFile.txt")) //for testing purposes, not really needed
            {
                swr.WriteLine("   *** BOUNCING BALL DATA ***");
                swr.WriteLine();
                swr.WriteLine("Time =    Hor displ =      Height =        Velo =");

                for (int i = 0; i < 100; i++)
                {
                    swr.WriteLine("{0:f2}        {1:f2}            {2:f5}           {3}   ", t0, hDis0, h0, Vel0);

                    t = t0 + InputData.timeIncr;                            //time
                    hDis = hDis0 + InputData.velocity * InputData.timeIncr; //length
                    Vel = Vel0 - InputData.g * InputData.timeIncr;          //velocity
                    h = h0 + 0.5 * (Vel + Vel0) * InputData.timeIncr;       //length
                    if (h <= 0)    //we have a bounce, recalculate Vel and h
                    {
                        //time required for the ball to hit the ground
                        double t1 = InputData.timeIncr * h0 / (h0 - h);                       
                        //velocity immediatly before impact
                        double V = Vel0 - InputData.g * t1;                      
                        //velocity immediatly after impact
                        double Vel1 = -InputData.bounceCoeff * V;                       
                        //velocity at the end of the time increment will be:
                        Vel = Vel1 - InputData.g * (InputData.timeIncr - t1);                      
                        //the new vertical displacement wil be:
                        h = h0 + 0.5 * (Vel1 + Vel) * (InputData.timeIncr - t1);
                        BounceCount++;
                        if (BounceCount > InputData.numBounces)
                        {
                            break; // Leave loop, we are done
                        }
                    }
                    PlotValues.Add(h);
                    t0 = t;
                    hDis0 = hDis;
                    Vel0 = Vel;
                    h0 = h;
                }
            }
            hDistLbl.Text = hDis0.ToString();
            timeLbl.Text = t0.ToString();
        }
DdoubleD 315 Posting Shark

Hey buddy, I tried to change the parameters (InputData form): Gravity(5) and Height(.5) and got an exception... I tried it with other parameters before that though and it's kind of cool!

EDIT: P.S. I noticed this snippet this morning, but didn't have time to download and play with it. Anyway I was daydreaming about simulating the bounce in real (relative) time...LOL--you are the math man, think you could do it?

ddanbe 2,724 Professional Procrastinator Featured Poster

Thanks for the feedback DdoubleD! And thanks for reporting the flaw!
The reason why this happened was quickly found.

private void DisplayPnl_Paint(object sender, PaintEventArgs e)
        {    
            Graphics G = e.Graphics;
            Panel P = sender as Panel;
            Plot pos = new Plot(P.ClientSize);
            pos.SetPlotPort(0, 100, 0, (int)InputData.height);

            for (int i = 0; i < PlotValues.Count; i++)
            {
                pos.PlotBitmap((double)i, PlotValues[i], B.GetBitmap(), G);
                //pos.PlotPixel((double)i, PlotValues[i], Color.Black, G);
            }
        }

If you change line 6 of this paint eventhandler in the main form into
pos.SetPlotPort(0, 100, 0, 5);Now the Y coordinate of the drawarea is going from 0 to 5 meters instead of from 0 to 0, when you use a height of 0.5!!!(Casting to an int gives zero for 0.5)
I was thinking of tennis and forgot there is also ping pong:$
Change the values in this statement to anything you like. I leave it as an exercise to input them through the inputform. The 100 here has to be multiplied by the time increment to get a value in meters for the X coordinate. Also notice that the starting velocity is horizontal. So for golf this makes little sense, perhaps someone could implement that and call it version 2? In fact this was all done quick and dirty my ain intention was to let one form talk to another.
BTW I am not a mathematician, but you could say I am mad about math;)

therese_1 0 Newbie Poster

is this java or c++??

JamesCherrill 4,733 Most Valuable Poster Team Colleague Featured Poster

If you can't tell the difference then this application is too advanced for your current level of knowledge

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.