Animating Simulations

by Lin Jensen, Bishop's University

written in Turbo Pascal 6.0 or 7.0

The simulation toolboxes provide processes in the
units: COPROC, PROC_MAN, (and MESSAGE)
{NOTE: Use a version of Proc_Man of November 25, 1996 or later }

This is the documentation for unit Animator.

Topics:


Unit Animator;

Animate simulations of the simulation toolbox
Author: Lin Jensen, Bishop's University

This unit adds to our toolboxes the global object

You will probably want to redefine AnimationObj methods Draw and Erase to properly represent your object. The default is to Draw a * and erase to a "footprint" defined here as a CONST.
  Footprint = '.' ;
{use space for erase, or other char, such as dot(.) to leave a "footprint"}

To animate a simulation, instead of Monitor.Runsimulation, call

     ANIMATE (SimLength, Samples, SimSlice, RealSlice);
-- where also, if anything moves, you are responsible for changing the coordinates in AnimationObj.new (fields x,y:real)
THIS IMPLEMENTATION is for a text screen. I expect somebody will modify it for graphics screen. START and STOP should enter and leave graphics mode, each DRAW and ERASE will have to be modified to use graphics. Everything else can stay as is.

TYPEs declared:

anAnimator = object
      Animating : boolean;   {is animation in progress?}
   constructor init;
   procedure RUNAnimatedSIMULATION (SimLength : REAL; SampleSize : WORD;
                        SimSlice, RealSlice : REAL);
      {USE in place of Monitor.runSimulation}

 {---- redefine these for graphics screen -----------}
   procedure Start; virtual;    {Animation screen}
   procedure Stop;  virtual;
  private
    AnimationList : FifoQueue; {of AnimationObj}
    lastshown: real;           {real time timer used by show}
   procedure Show (timeslice:real);
             {show animation after real time interval, in seconds}
end;

position = record { screen coordinates } x,y:real; end;
AnimationPtr = ^AnimationObj; AnimationObj = object (queueElement) old:position; {where last shown, used by Update} new:position; {--this one you must keep current as appropriate} constructor Init (x, y:real {initial position}); procedure Update; procedure Draw; virtual; procedure Erase; virtual; destructor destroy; virtual; {take out of list} end;

A specific animator for queues shows the length of the queue as a bar of variable length. It stays rooted at its initial position. As a practical matter we need to limit the length shown, if the queue gets longer, a shaded square will be shown at the end.

A QueueAnimator gets associated with its queue by the parent pointer, it uses this to find out the current length of its queue. You pass the queue to Init as a parameter, it sets up the pointer.

QueueAnimator = object (AnimationObj)
   parent : ^list;
   MaxLen : integer;         {max. length shown}
   constructor init (x,y:real; maxShown: integer; VAR Q:list);
   procedure Draw; virtual;
   procedure Erase; virtual;
end;

VAR
   ANIMATIONMonitor : anAnimator; {a global object initialized in this unit}

procedure ANIMATE (SimLength : REAL; SampleSize : WORD;
                   SimSlice, RealSlice : REAL);
ANIMATE will now be used in place of Monitor.RunSimulation. In fact it is simply short for AnimationMonitor.RunAnimatedSimulation .

Example: Non-bouncing ball

Anything we wish to animate must have an
animationObj associated with it.
Ideally we would want to use multiple inheritance to inherit also the properties of an animationObj. Unfortunately, Turbo Pascal doesn't allow this, so we have to associate it in some other way.
For example, a QueueAnimator has a pointer to its parent queue, which it uses to get the queue's length (parent^.Card).

In the next example, drawer is a field of the ball object. This uses the default Draw procedure, (currently, a * is drawn.)

  ballp = ^ ball;
  ball = object (procdescr)
     drawer : animationObj;
     procedure lifecycle; virtual;
     procedure calc(VAR p:position);
  end;
Ball happens to have a life cycle. This quasi-continuous object uses its own time slice to periodically update its position, in the new field of drawer.
  procedure ball.lifecycle;
  const slice = 0.5;
  begin
     drawer.init(1,1);      {throw from upper left}
     while true do
       begin
        Sleep_for (slice);
        calc (drawer.new);  {update ball's position}
       end;
  end ; {ball.lifecycle}
The procedure calc calculates the ball's position whenever it is called. Actually it should determine position based on last position, velocity, and timeslice, then possibly update velocity based on acceleration. This example is "pre-cooked" to give the parabola we expect.
 
  procedure ball.calc (VAR p:position);
  begin
     with p do begin
        x:= 0.5 + time*0.75;    {constant horizontal velocity}
        y := sqr(time/15)+0.5;  {falling}
        if y > 24 then  y:=24;  {stick to ground and roll} 
     end;
  end;
That is all we need to do, besides readying the ball process and calling ANIMATE in the main program (instead of RunSimulation). Since drawer has been initialized, the animation of the ball will be seen.

The full example program does this, and it also shows a QueueAnimator in action. It runs for 10 seconds of real time, 100 simulation clock units (shown at lower right.)


Last updated 26 November 1996

Up to top of this document
Back to Simulation notes. home arrow

Comments, etc, send to

ljensenn at ubishops.ca