CannonTrajectory

version 1.0
© 2003 Bernard Schutz

Investigate the most elementary problem of motion in gravity: the trajectory of a cannonball near the surface of the Earth.

Contents


Description of CannonTrajectory

The unit called CannonTrajectory implements the Java program for computing the trajectory of a cannonball or other projectile fired from the surface of the Earth, at an arbitrary angle and speed. Users can experiment to see what angle gives the greatest range.

This program is the most elementary one in the book, and many later programs are based on it.It is introduced in Investigation 1.3.  It introduces the important programming concept of loops.


Using CannonTrajectory

If you have not used Triana before, read the instructions first. To use this program, simply drag the CannonTrajectory icon from the toolbox into the working area, drag the SGTGrapher unit into the area, and connect the two if Triana has not already connected them for you. If you press the Start button then the program will execute with its default settings, namely with a speed of 100 ms-1, a firing angle of 45o to the horizontal, and a time-step for the simulation of 0.1s. A graph window will pop up showing the trajectory.

Initially this window will not necessarily use the same scale for distances in the horizontal and vertical direction, since it is set by default to adjust the scales to show the most information about the curve. Users are recommended to change this so that the vertical and horizontal scales are the same. Go to the "Plot" menu of the SGTGrapher display, and select "force equal ranges on both axes", then press "Reset Zoom".  

You can change the initial speed, angle, and time-step by using the parameter window of the unit, illustrated here. Double-click on the CannonTrajectory unit in the working area to get this window. Either adjust the slider by dragging it with the mouse, or type a new value into the boxes at the right of each slider, followed by the "Apply" key. Experiment with the angle to see what angle gives the maximum range. Make the time-step longer to see what effect this has. If you choose a speed that makes the curve go off the scales you have chosen for the graph window, use the Zooming window described in the previous paragraph to re-set the scale maximum values to larger ones so that you can see it all.


Understanding CannonTrajectory

The Java program for CannonTrajectory is given in the last section. It is introduced in the book in Investigation 1.3. For general remarks about how to read and understand Java programs see the Introduction to Programming in Java. The central idea of this program, as for most programs, is to get the computer to do a certain set of steps over and over again. In this case, the steps are to compute how the cannonball moves along its trajectory one small step at a time. The program construction that repeats is called a loop.

Loops are described in the Introduction to Programming in Java. For CannonTrajectory, the important issues are: 1) what steps to we want to repeat,  2) how do we start the calculation, and 3) how do we decide when to stop the calculation?
 

  1. The steps to repeat are contained in the loop that starts with the for statement in the listing below and finishes at its final closing bracket. Each step assumes that the projectile has a height given by the value of h, a horizontal position given by the value of x,  a vertical speed given by the value of v, and a horizontal speed given by the value of u at the beginning of the step. The first line of code moves the projectile horizontally. Since there is no horizontal acceleration, it just multiplies u by the time-step dt and adds that to the existing value of x to get the new value of x. This line looks like this:

  2.              x = x + u*dt;
    This statement looks strange if you interpret it as algebra, but it makes sense if you interpret the "=" sign as assignment, i.e. as computing the value of x + u*dt using the initial value of x and then placing this new number into the variable x, wiping out its initial value. This means that, after this statement is executed, x has the new value equal to the horizontal position at the current time-step.

    The next step inside the loop is to compute the change in the vertical speed due to the downward acceleration. Since the speed going into this step was v, the new value of the vertical speed is v - g*dt, since we are using the variable g to hold the value of the acceleration of gravity. Instead of assigning this to v in a way analogous to the way we reassigned the new horizontal position value to x above, we assign this to a different variable w:
                  w = v - g*dt;
    This is because we still need the old value of v a little later on, so we temporarily keep its new value in w.

    Now we can compute the new vertical position of the cannonball. We could simply advance its position by using the new value of the vertical speed, w. This would give us a statement like h = h + v*dt. But this is less accurate than what we actually do in the program, which is to use the average value of the vertical speed during the interval of time dt. The average value is (v + w)/2, so the actual statement is
                  h = h + (w + v)/2*dt;
    Notice that this is a re-assignment to the variable h, just as we did for x above. The need to use the old speed v in this expression is the reason that we introduced the variable w above.

    We want to save the values of the position variables so we can plot the graph after the calculation is finished. This means they should be stored in the arrays we defined for this purpose. We execute the statements
                 verticalDistance[j] = h;
                 horizontalDistance[j] = x;
    For an explanation of the meaning of the notation "[j]" see the general remarks on arrays in the Introduction to Programming in Java. It means that the j'th element of the vector verticalDistance will contain the value of h, and the j'th value of horizontalDistance will contain the value of x for this step.

    Finally, we can dispense with w, which was just a temporary storage location. We want v to contain the most recent value of the vertical speed when the loop repeats, so before the repeat we have to execute the statement
                   v = w;
    This ensures that at the next step all the variables have their intended meanings.

  3. To start the calculation in the first place, we declare all the variables used to manage the computation and then set up the initial values of h, x, and v before going into the loop. The program defines default values of the speed (using a variable called "speed"), the angle of firing (called "angle") and the time-step "dt". These variables will, however, be set by Triana to whatever values the user gives them in the parameter window. Then the program defines the variable g to equal the acceleration of gravity, so that it can conveniently be used in later expressions. This is all in the preliminary material. Inside the main program (whose name is process), we set up x and h to be zero, and we ensure that u and v hold the components of the speed computed by using the trigonometric functions with the value of speed. We declare the arrays verticalDistance and horizontalDistance to have 1000 elements. This is the maximum number of time-steps that we allow the program to execute. We discuss this limitation below.
  4. Finally, we have to ensure that the program stops. Infinitely repeating loops, which never stop, are a bane of programmers. We don't want that here! We have to implement two alternative ways of stopping the loop. The first is purely a programming issue: we must not make more than 1000 steps, because otherwise we will begin to assign values to non-existent elements of verticalDistance and horizontalDistance, which would cause a Java error and the program would be stopped by Java.

  5. The second way of stopping is more physical: when the cannonball hits the ground again, then we should stop, since our assumption of free fall is no longer correct! Our implementation of this condition is crude. We simply test the value of h at the end of each step in the loop. If h is negative, then we do not do any more steps. The for statement that begins the loop contains the test that must be passed for steps to continue. In our case this is:
                  ( h >= 0.0 ) && ( j < 1000 )
    This is a logical expression with two sub-expressions. (To learn about logic statements, see the Introduction to Programming in Java.) The first sub-expression is ( h >= 0.0 ), which evaluates to the logical value true if h is positive or zero. The second sub-expression is ( j < 1000 ), which evaluates to true if the index j is less than 1000. The two parts are joined by the "&&" operator, which means logical AND in Java. The resulting overall expression therefore evaluates to true only if both sub-expressions are true, and to false if either of the sub-expressions is false. Thus, the loop repeats if and only if the projectile is still in the air and we have not had too many steps.

    Notice that j must be less than 1000; it is not allowed to equal 1000, even though the arrays have been set up to have 1000 elements. This is because in Java array index values begin at 0. So an array with 1000 elements has a maximum index value of 999. This is the maximum allowed value for j.

The other issue in the program, besides the handling of the loop, is to get the data ready for display. This is a technical Triana issue. This is taken care of in two steps. The first is to repackage the data into arrays that are shorter than 1000 elements if the projectile has returned to the ground before we executed 1000 steps. We do this repackaging into finalHorizontal and finalVertical so that only the interesting data are sent to the graphing unit. Otherwise the grapher would draw a line at zero height to the right of the trajectory, up to the distance represented by 1000 steps.

The second step in creating output is to package it up in a data type that Triana calls a Curve. This contains the two data arrays plus the name of the data (called its title) and the labels for the horizontal and vertical axes. We define the Curve to contain the data in the step
        Curve out = new Curve( finalHorizontal, finalVertical );
We then define its labels with functions built into Triana for this purpose:
        out.setTitle("Trajectory of projectile");
        out.setIndependentLabels(0,"horizontal distance (m)");
        out.setDependentLabels(0,"vertical distance (m)");
And finally we output this data set to the output node with the final statement of the program:
        output( out );
When this data arrives at Triana's graphing unit, it knows enough about the Curve data type to interpret the two arrays as containing the x- and y-coordinates of the points to be plotted.
 


Suggested modifications of CannonTrajectory

The above discussion suggests some ways that you might change the program, besides simply modifying the parameter values. If you want to change the program you will have to re-compile it, as explained by the help file Using Triana for Gravity from the ground up.

The first change you might experiment with is to make the vertical steps less accurate by replacing
              h = h + (w + v)/2*dt;
with
              h = h + w*dt;
See what the effect of changing the time-step is on the accuracy of the calculation.You should find that it becomes more noticeable if you make the time-step bigger.

The second change could be to make the ending of the trajectory more accurate. At present, the first negative value of h ends the loop, but is stored as the value of the height. Really, the trajectory has ended between this last time-step and the previous one. You might try to calculate, from the given data, an approximation to the horizontal distance at which the height went to zero, and use these values as the last values in the stored data array.
 


Listing of the Java code for CannonTrajectory

 

Preliminary definitions of parameters and constants

    /*
       g is the acceleration of gravity in meters per second per second
    */

    private double g = 9.8;

    /*
      dt is the time-step in seconds. Its value for any run is set by
      the user in the parameter window.
    */

    private double dt;

    /*
       speed is the launch speed in meters per second. Its value for any
       run is set by the user in the parameter window.
    */

    private double speed;

    /*
       angle is the launch angle in degrees, measured from the horizontal.
       Its value for any run is set by the user in the parameter window.
    */

    private double angle;
 

Program code

    /*
      Begin the program.
    */
    public void process() throws Exception {
 

        /*
          Initialize the calculation:
          - Define horizontalDistance and verticalDistance to be arrays
            holding the horizontal distance and height reached by the
                cannonball at each time-step. Give them length 1000 to allow
            for up to 1000 time-steps in the trajectory.
          - Define x and h to be the variables used to store these distances
            temporarily at each time-step of the calculation.
          - Convert the input angle to radians so we can use trig functions.
            Use the Java built-in value of pi, called Math.PI.
          - Define u and v to be the horizontal and vertical components
            of the velocity; compute their values from the speed and
            angle chosen by the user. Use the trig functions built into
            Java, called Math.cos and Math.sin.
          - Introduce w, a variable that stores an intermediate value
            of the vertical speed.
          - Set the initial values stored for the height
            and distance to zero.
          - Define an integer variable j to count steps.
        */

        double[] horizontalDistance = new double[1000];
        double[] verticalDistance = new double[1000];
        double x = 0;
        double h = 0;
        double theta = angle * Math.PI / 180.0;
        double u = speed * Math.cos( theta );
        double v = speed * Math.sin( theta );
        double w;
        horizontalDistance[0] = 0;
        verticalDistance[0] = 0;
        int j;

        /*
          Now enter the loop that computes each time step in succession.
          The variable j is a counter: it starts at 1 and increases
          by one at each step of the loop.
          There should be no more than 1000 time-steps, since that is
          the size of the array we have defined to hold the data. But
          the calculation should end when the height is negative,
          meaning that the cannonball has returned to the ground and
          would actually be below it if the ground were not there.
          Therefore the condition for continuing the loop is
                 ( h >= 0.0 ) && ( j < 1000 )
        */

        for ( j = 1; (( h >= 0.0 ) && ( j < 1000 )); j++ ) {

            /*
              At each step in the loop the value of x starts out as
              the horizontal distance at the previous step. Increase
              it by the distance traveled in time dt so that it now has
              the new value of the distance.
            */
            x = x + u*dt;

            /*
              At each step in the loop the value of v starts out as
              the vertical speed at the previous one. Since we want to
              use the average of the vertical speeds at two time-steps,
              keep v unchanged at first, and define w to be the speed
              at the present time-step. Thus, w equals v diminished by
              the downward acceleration.
            */
            w = v - g*dt;

            /*
              Now follow the rule described in the text, that the
              change in vertical height depends on the average speed
              over the time-interval, in other words on the speed
                             (w + v )/2.
              Multiply this speed by the interval of time dt and
              increase the height by this amount. Note that the speeds
              eventually will become negative because of the previous
              line of code, so that eventually h will begin to decrease.
            */
            h = h + (w + v)/2*dt;

            /*
              Now store the calculated height and horizontal distance in
              the arrays defined for them. This allows us to re-use h and
              x for the new values at the next time-step without losing
              the values we have computed for this time-step.
            */
            verticalDistance[j] = h;
            horizontalDistance[j] = x;

            /*
              Finally update the variable v so that it stores the vertical
              speed for the present time-step. That way, at the next step
              in the loop it will contain the "old" speed, as required
              for the averaging of speeds described above.
            */
            v = w;

        }
        /*
          The closing bracket above is the end of the group of statements
          that form the loop. The computer increases j here and tests to
          see if it should do another step in the loop. If so it goes
          back to the first statement after the opening bracket at the end
          of the "for" statement above. If not it goes to the next statement.
        */

        /*
          We have now exited from the loop. That means that either the height
          is negative (the cannonball has returned to the ground), or the
          loop has run through 1000 steps before the cannonball returned.
          In the latter case, the user will see from the output that the
          trajectory is not ended, and the whole thing should be run again
          with a larger choice of time-step.
          For the case where the cannonball returns before we run out of
          the allocated number of steps, the variable j is equal to one
          more than the number of steps, since it got increased at the end
          of the final loop step, after the last values were stored. So
          we define two new arrays of exactly the length needed to hold
          the data, copy the values into them, and then output the result
          so that it can be examined, printed, or graphed. (We have to go
          to the trouble of defining new arrays since, in Java, it is not
          possible to re-define the length of an array without losing the
          data stored in it.) The new arrays should have length j since
          they contain the initial values as well as the values at all the
          time-steps.
          The output is defined to be an object called a Curve, which is a
          data type defined in Triana. It contains not only the data but
          also the labels that can be used by the grapher. So we set here
          the title and the horizontal and vertical axis labels.
        */

        double[] finalHorizontal = new double[j];
        double[] finalVertical = new double[j] ;

        for ( int k = 0; k < j; k++ ) {
            finalHorizontal[k] = horizontalDistance[k];
            finalVertical[k] = verticalDistance[k];
        }
 
        Curve out = new Curve( finalHorizontal, finalVertical );
        out.setTitle("Trajectory of projectile");
        out.setIndependentLabels(0,"horizontal distance (m)");
        out.setDependentLabels(0,"vertical distance (m)");
        output( out );

    }



Return to index of all computer programs.