

import java.awt.event.*;
import javax.swing.*;
import com.genlogic.*;

//////////////////////////////////////////////////////////////////////////
// This program simulates a missile fight. It creates a number of planes
// and missiles, and animates them. Every missile is following one plane 
// until it destroies the plane. Initailly, the missiles are going slower
// than the planes, but eventually they are catching up.
//
// This demo is written using the methods of the GlgBean.
// As an alternative, the methods of underlying GlgObjects may be used.
// For example, the following line:
//       plane_template = GetResourceObject( map_viewport, "plane" );
// may be rewritten as:
//       plane_template = map_viewport.GetResourceObject( "plane" );
//////////////////////////////////////////////////////////////////////////
public class GlgAircombatDemo extends GlgJBean implements ActionListener
{
   static final long serialVersionUID = 0;

   //////////////////////////////////////////////////////////////////////////
   // Support class, keeps data for one airplane or missile
   //////////////////////////////////////////////////////////////////////////
   class Plane
   {
      GlgObject object;
      double speed;
      int change_state;
      int correcting_distance;
      boolean destroyed;
      int missile_delay;
      GlgPoint position;
      GlgPoint direction;
      GlgPoint angle;
      GlgPoint angle_change;
      
      ////////////////////////////////////////////////////////////////////////
      Plane()
      {
         object = null;
         speed = 0;
         change_state = 0;
         correcting_distance = 0;
         destroyed = true;  // No graphics created for the plane yet
         missile_delay = 0; // Prevents from changing for
           // some time after a turn to let the plane correct the distance
             
             position = new GlgPoint( 0., 0., 0. );
         direction = new GlgPoint( 0., 0., 0. );
         angle = new GlgPoint( 0., 0., 0. );
         angle_change = new GlgPoint( 0., 0., 0. );
      }
      
      ///////////////////////////////////////////////////////////////////////
      // A partial copy only
      ///////////////////////////////////////////////////////////////////////
      void CopyPlaneInfoFrom( Plane from )
      {
         this.position.x = from.position.x;
         this.position.y = from.position.y;
         this.position.z = from.position.z;
         
         this.angle.x = from.angle.x;
         this.angle.y = from.angle.y;
         this.angle.z = from.angle.z;
         
         this.angle_change.x = from.angle_change.x;
         this.angle_change.y = from.angle_change.y;
         this.angle_change.z = from.angle_change.z;
         
         this.change_state = from.change_state;
         this.correcting_distance = from.correcting_distance;
      }
   }

   //////////////////////////////////////////////////////////////////////////
   // The main demo class
   //////////////////////////////////////////////////////////////////////////

   // Some constants defining simulation parameters.
   static final double
     MAX_DISTANCE      = 700.,   // Max allowed distance from a center 
     CHANGE_THRESHOLD  = 0.99,  // The number in the range [0;1]; defines 
                                     // how often planes change direction. 
     PLANE_SPEED       = 10.,    // Plane's speed 
     MISSILE_SPEED     = 0.6;   // Initial speed relatively to the plane
   static final int
     TRANSITION_NUM    = 20,    // The number of steps required for a turn 
     DELAY             =  2,    // Delay after a turn 
     MISSILE_DELAY     = 10;    // Missile adjustment delay 

   // Some global simulation objects and variables. 
   boolean Use3D = true;
   int
     UpdateSpeed = 30,
     NumPlanePairs = 10,
     NumPlanes = NumPlanePairs * 2,
     SelectedPlane = -1;
   double PlaneScale = 1.;
   GlgObject 
     map_viewport,
     plane_template_3D,
     plane_template_2D;	
   GlgPoint
     Center = new GlgPoint( 0., 0., 0. ),
     buf_position1 = new GlgPoint( 0., 0., 0. ),
     buf_position2 = new GlgPoint( 0., 0., 0. ),
     stored_color;
   Plane buf_plane = new Plane();

   // Array to keep the state of the simulation (both planes and missiles)
   Plane Planes[] = new Plane[ NumPlanes ];

   boolean IsReady = false;
   Timer timer = null;

   // Functions to convert between degrees and radians. 
   double RAD( double angle ) { return angle / 180. * Math.PI; }
   double DEG( double angle ) { return angle * 180. / Math.PI; }

   boolean IS_MISSILE( int plane ) { return ( plane % 2 ) != 0; }
   boolean IS_PLANE( int plane ) { return ! IS_MISSILE( plane ); }

   //////////////////////////////////////////////////////////////////////////
   public GlgAircombatDemo()
   {
      super();
   }

   //////////////////////////////////////////////////////////////////////////
   // Stops the update thread
   //////////////////////////////////////////////////////////////////////////
   public void stop()
   {
      StopUpdate();

      IsReady = false;
      super.stop();
   }

   //////////////////////////////////////////////////////////////////////////
   public void StartUpdate()
   {
      if( timer == null )
      {
         timer = new Timer( UpdateSpeed, this );
         timer.setRepeats( true );
         timer.start();
      }
   }

   //////////////////////////////////////////////////////////////////////////
   public void StopUpdate()
   {
      if( timer != null )
      {
         timer.stop();
         timer = null;
      }
   }

   //////////////////////////////////////////////////////////////////////////
   // Initializes the simulation and starts the update thread
   //////////////////////////////////////////////////////////////////////////
   public void ReadyCallback()
   {
      if( GetJavaLog() )
        PrintToJavaConsole( "Debug: Ready\n" );

      super.ReadyCallback();

      map_viewport = GetResourceObject( null, "Map" );

      // Find a plane's template in the drawing
      plane_template_3D = GetResourceObject( map_viewport, "plane" );
      plane_template_2D = GetResourceObject( map_viewport, "plane2D" );
      SetSResource( plane_template_2D, "Name", "plane" );
      SetDResource( plane_template_2D, "Visibility", 1. );

      // Delete the template from the drawing 
      DeleteObject( map_viewport, plane_template_3D );
      DeleteObject( map_viewport, plane_template_2D );

      // Populate the drawing with planes: copy the plane template
      CreatePlanes( NumPlanes );

      SetDResource( "Info/Visibility", 0. );
      SetDResource( "Quit/Visibility", 0. );

      Update();

      StartUpdate();
      IsReady = true;
   }

   //////////////////////////////////////////////////////////////////////////
   // For use as a stand-alone java demo
   //////////////////////////////////////////////////////////////////////////
   public static void main( final String arg[] )
   {
      SwingUtilities.
        invokeLater( new Runnable(){ public void run() { Main( arg ); } } );
   }

   //////////////////////////////////////////////////////////////////////////
   public static void Main( final String arg[] )
   {
      class DemoQuit extends WindowAdapter
      {
         public void windowClosing( WindowEvent e ) { System.exit( 0 ); }
      } 

      GlgObject.Init();

      JFrame frame = new JFrame();

      GlgAircombatDemo aircombat = new GlgAircombatDemo();
      
      frame.setResizable( true );
      frame.setSize( 600, 600 );
      frame.setLocation( 20, 20 );

      frame.getContentPane().add( aircombat );

      frame.addWindowListener( new DemoQuit() );
      frame.setVisible( true );

      // Setting the viewport triggers the ReadyCallback which starts
      // the update thread.
      //
      aircombat.SetViewport( GlgObject.LoadWidget( "aircombat.glg",
                                                  GlgObject.FILE ) );
   }

   //////////////////////////////////////////////////////////////////////////
   // Creates planes by copying the template and adds the planes to the
   // drawing. Annotates missiles with a different size and color.
   //////////////////////////////////////////////////////////////////////////
   void CreatePlanes( int num_planes )
   {
      int i;
      
      for( i=0; i<num_planes; ++i )
      {
         // Create a copy of a plane template and add it to the drawing 
         Planes[ i ] = new Plane();

         Planes[ i ].object = 
           CopyObject( Use3D ? plane_template_3D : plane_template_2D );
         SetSResource( Planes[ i ].object, "Name",
                      "plane" + new Integer( i ).toString() );

         AddObjectToBottom( map_viewport, Planes[ i ].object );

         // Set initial simulation parameters 
         Planes[ i ].destroyed = false;
         Planes[ i ].correcting_distance = 0;
         Planes[ i ].missile_delay = 0;

         // Set a random initial position 
         Planes[ i ].position.x = -1000. + 1000. * GetData();
         Planes[ i ].position.y = -1000. + 1000. * GetData();
         Planes[ i ].position.z = -1000. + 1000. * GetData();

         if( IS_MISSILE( i ) )
         {
            // Set missiles initial speed to be lower 
            Planes[ i ].speed = MISSILE_SPEED * PLANE_SPEED;

            // Annotate the missile with a different size and red color 
            SetDResource( Planes[ i ].object, "scale", 0.5 );
            SetGResource( Planes[ i ].object, "Color", 1., 0., 0. ); // Red
           }
         else   // Plane 
           Planes[ i ].speed = PLANE_SPEED;

         Planes[ i ].change_state = 1;
         SetPlane( i );     // Set initial plane parameters. 
         Planes[ i ].change_state = 0;
      }

      AdjustPlanesScale( PlaneScale ); // Set initial plane scale 
   }

   //////////////////////////////////////////////////////////////////////////
   // Simulates a plane or a missile.
   //////////////////////////////////////////////////////////////////////////
   void AutoPilot( int plane )
   {
      if( Planes[ plane ].destroyed )
        return;

      if( IS_PLANE( plane ) )
      {
         if( Distance( Planes[ plane ].position, Center ) > MAX_DISTANCE )
         {
            // Change direction to correct the distance from a center 
            if( Planes[ plane ].correcting_distance == 0 )
              ChangeDirection( plane, true );
         }
         else if( GetData() > CHANGE_THRESHOLD )
           ChangeDirection( plane, false );  // Random direction change 
      }
      else   // Missile 
      {
         ++Planes[ plane ].missile_delay;
         if( ( Planes[ plane ].missile_delay % MISSILE_DELAY ) == 0 )
           // Adjust missile to the new plane position 
           ChangeDirection( plane, false );
      }

      // Move the plane by one simulation step. 
      MakeStep( plane, 1, null );
   
      if( !Planes[ plane ].destroyed )
        SetPlane( plane );  // Set the plane in the drawing 
   }

   //////////////////////////////////////////////////////////////////////////
   // For a missile: adjust the missile's direction to point to the plane it
   //   follows.
   // For a plane:
   //   a) if required==True, change the plane direction to correct the
   //      distance from the center to avoid going off the screen.
   //   b) if required==False, randomly change the plane's direction to make
   //      it more complicated for a missile to catch it.
   //////////////////////////////////////////////////////////////////////////
   void ChangeDirection( int plane, boolean required )
   {
      double dx, dy, dz, angle_y, angle_z;

      if( IS_PLANE( plane ) )
      {
         // double distance = Distance( Planes[ plane ].position, Center );
         
         // Start a new change and set flags to indicate a change in progress 
         Planes[ plane ].change_state = TRANSITION_NUM - 1; 
         if( required )
           Planes[ plane ].correcting_distance = DELAY * TRANSITION_NUM + 1;
      
         while( true )
         {
            // Choose a new random direction 
            Planes[ plane ].angle_change.x = 360. * GetData() / TRANSITION_NUM;
            Planes[ plane ].angle_change.y =
              ( -30. + 60. * GetData() ) / TRANSITION_NUM;
            Planes[ plane ].angle_change.z = 360. * GetData() / TRANSITION_NUM;

            // Test if it lowers the distance from the center, if required. 
            if( !required ||
               DecreaseDistance( plane, 0, DELAY * TRANSITION_NUM + 1 ) )
              break;
         }
      }
      else  // Missile 
      {
         // Calculate the angles to direct the missile at the plane. 
         dx = Planes[ plane - 1 ].position.x - Planes[ plane ].position.x;
         dy = Planes[ plane - 1 ].position.y - Planes[ plane ].position.y;
         dz = Planes[ plane - 1 ].position.z - Planes[ plane ].position.z;

         if( dx == 0. )
           angle_y = ( dz > 0. ? 270. : 90. );
         else
         {
            angle_y = Math.atan( dz / dx );
            angle_y = DEG( angle_y );
            if( dx > 0. && dz > 0. || dx < 0. && dz < 0 )
              angle_y += 180.;
         }
         
         if( dx == 0. )
           angle_z = ( dy > 0. ? 90. : 270. );
         else
         {
            angle_z = Math.atan( dy / dx );
            angle_z = DEG( angle_z );
            if( dy < 0. && dx < 0. || dy > 0. && dx < 0. )
              angle_z += 180.;
         }
         
         if( angle_y > 180. )
           angle_z += 180.;
         
         // Change missile direction instantly (it has a much smaller inertia). 
           
         // Take from a plane. 
         Planes[ plane ].angle.x = Planes[ plane - 1 ].angle.x; 
         Planes[ plane ].angle.y = angle_y;
         Planes[ plane ].angle.z = angle_z;
         Planes[ plane ].change_state = 1;

         // Randomly speed the missile up until it is faster then the plane. 
         if( Planes[ plane ].speed < 1.2 * PLANE_SPEED && GetData() > 0.5 )
           Planes[ plane ].speed *= 1.04;
      }
   }

   //////////////////////////////////////////////////////////////////////////
   // Calculate and return a distance between two positions.
   //////////////////////////////////////////////////////////////////////////
   double Distance( GlgPoint position1, GlgPoint position2 )
   {
      double dx, dy, dz;

      dx = position1.x - position2.x;
      dy = position1.y - position2.y;
      dz = position1.z - position2.z;
      
      return Math.sqrt( dx * dx + dy * dy + dz * dz );
   }

   //////////////////////////////////////////////////////////////////////////
   /// Tests whether or not the distance will decrease after the required
   /// number of simulation steps. Compares the distance after steps1 number
   /// of steps with the distance after steps2 number of steps.
   ///////////////////////////////////////////////////////////////////////////
   boolean DecreaseDistance( int plane, int steps1, int steps2 )
   {
      MakeStep( plane, steps1, buf_position1 );
      MakeStep( plane, steps2, buf_position2 );
      return ( Distance( buf_position2, Center ) <
              Distance( buf_position1, Center ) );
   }

   //////////////////////////////////////////////////////////////////////////
   // Makes the requested number of simulation steps and returns the
   // calculated postion of a plane after that.
   // If the position_value parameter is not NULL, it is a trial step; the
   // calculated values are returned in this structure and the plane
   // parametrs are restored afterwards.
   // If the parameter is NULL, it is a real step affecting the plane.
   //////////////////////////////////////////////////////////////////////////
   void MakeStep( int plane, int num_steps, GlgPoint position )
   {
      int i;

      if( position != null )
        buf_plane.CopyPlaneInfoFrom( Planes[ plane ] );     // Save

      for( i=0; i<num_steps; ++i )
      {
         // Make a step in the current direction 
         Planes[ plane ].position.x += Planes[ plane ].speed *
           Math.cos( RAD( Planes[ plane ].angle.z ) ) *
             Math.cos( RAD( Planes[ plane ].angle.y ) );
         Planes[ plane ].position.y += Planes[ plane ].speed *
           Math.sin( RAD( Planes[ plane ].angle.z ) ) *
             Math.cos( RAD( Planes[ plane ].angle.y ) );
         Planes[ plane ].position.z += Planes[ plane ].speed *
           Math.sin( RAD( Planes[ plane ].angle.y ) ) *
             Math.cos( RAD( Planes[ plane ].angle.z ) );

         // If a direction change is in progress, adjust the plane angles. 
         if( Planes[ plane ].change_state != 0 && IS_PLANE( plane ) )
         {
            Planes[ plane ].angle.x += Planes[ plane ].angle_change.x;
            Planes[ plane ].angle.y += Planes[ plane ].angle_change.y;
            Planes[ plane ].angle.z += Planes[ plane ].angle_change.z;
            
            if( Planes[ plane ].angle.x >= 360. )
              Planes[ plane ].angle.x -= 360.;
            if( Planes[ plane ].angle.y >= 360. )
              Planes[ plane ].angle.y -= 360.;
            if( Planes[ plane ].angle.z >= 360. )
              Planes[ plane ].angle.z -= 360.;
            
            --Planes[ plane ].change_state;
         }
         
         if( Planes[ plane ].correcting_distance != 0 )
           --Planes[ plane ].correcting_distance;
      }

      if( position != null ) // Just checking the distance: restore and return.
      {
         // Return the result 
           position.x = Planes[ plane ].position.x;
         position.y = Planes[ plane ].position.y;
         position.z = Planes[ plane ].position.z;
         
         Planes[ plane ].CopyPlaneInfoFrom( buf_plane );     // Restore
      }
      else  // Real step: if it is a missile, check if it got its plane. 
        if( IS_MISSILE( plane ) &&
           Distance( Planes[ plane - 1 ].position,
                    Planes[ plane ].position ) < 5. )
        {
           // Got it: Delete both the plane and the missile. 
           DeletePlane( plane - 1, false );
           DeletePlane( plane, false );
           
           // Restart if no more planes left. 
           if( NoPlanesLeft() )
           {
              CreatePlanes( NumPlanes );
              Update();
           }
        }
   }

   //////////////////////////////////////////////////////////////////////////
   // Deletes the plane or missile from the drawing. Flashes the size of a
   // missile for an explosive effect.
   //////////////////////////////////////////////////////////////////////////
   void DeletePlane( int plane, boolean explode )
   {
      if( plane == SelectedPlane )
        UnselectPlane();

      // Find and delete the plane from the drawing 
      if( !DeleteObject( map_viewport, Planes[ plane ].object ) )
      {
         PrintToJavaConsole( "Cannot find the plane.\n" );
         return;
      }
      Planes[ plane ].destroyed = true;
   }

   //////////////////////////////////////////////////////////////////////////
   // Updates the plane in the drawing with the new values. Ignores 3D
   // rotation for a missle.
   //////////////////////////////////////////////////////////////////////////
   void SetPlane( int plane )
   {
      // Plane is changing its direction. 
      if( Planes[ plane ].change_state != 0 )
      {
         SetDResource( Planes[ plane ].object, "angleX",
                      Planes[ plane ].angle.x );
         SetDResource( Planes[ plane ].object, "angleY",
                      Planes[ plane ].angle.y );
         SetDResource( Planes[ plane ].object, "angleZ",
                      Planes[ plane ].angle.z );

         if( IS_MISSILE( plane ) )     // Missile: an instant change 
           Planes[ plane ].change_state = 0;
      }
      
      SetGResource( Planes[ plane ].object, "move",
                      Planes[ plane ].position.x,
                      Planes[ plane ].position.y,
                      Planes[ plane ].position.z );
   }

   //////////////////////////////////////////////////////////////////////////
   // Makes one simulation step for every plane or missile.
   //////////////////////////////////////////////////////////////////////////
   void UpdateBattleField()
   {
      int i;

      if( !IsReady )
        return;

      for( i=0; i<NumPlanes; ++i )
        AutoPilot( i );

      UpdateSelectedPlane();
      
      // Update the drawing after changes. 
      Update();
   }

   //////////////////////////////////////////////////////////////////////////
   // Returns a random number in the [0;1] range.
   //////////////////////////////////////////////////////////////////////////
   double GetData()
   {
      return Math.random();
   }

   //////////////////////////////////////////////////////////////////////////
   void UpdateSelectedPlane()
   {
      if( SelectedPlane == -1 )
        return;

      SetDResource( "Info/XLocation", Planes[ SelectedPlane ].position.x );
      SetDResource( "Info/YLocation", Planes[ SelectedPlane ].position.y );
   }

   //////////////////////////////////////////////////////////////////////////
   void SelectPlane( int plane_number )
   {
      if( SelectedPlane == -1 )
        SetDResource( "Info/Visibility", 1. );
      else 
        // Restore color of the previously selected plane
        if( !Planes[ SelectedPlane ].destroyed )
          Planes[ SelectedPlane ].object.SetGResource( "Color", stored_color );

      // If missile, get the plane number
      if( IS_PLANE( plane_number ) )
      {
         SetSResource( "Info/InfoFormat", "Plane %.0lf" );
         SetDResource( "Info/PlaneNumber", (double) plane_number );
      }
      else
      {
         SetSResource( "Info/InfoFormat", "Missile %.0lf" );
         SetDResource( "Info/PlaneNumber", (double) ( plane_number / 2 ) );
      }

      SelectedPlane = plane_number;
      stored_color =
        new GlgPoint( Planes[ plane_number].object.GetGResource( "Color" ) );

      // Set color to yellow
      SetGResource( Planes[ plane_number].object, "Color", 1., 1., 0. );
   }

   //////////////////////////////////////////////////////////////////////////
   void UnselectPlane()
   {
      if( SelectedPlane == -1 )
        return;

      if( !Planes[ SelectedPlane ].destroyed )
        // Restore the color
        Planes[ SelectedPlane ].object.SetGResource( "Color", stored_color );

      SetDResource( "Info/Visibility", 0. );
      SelectedPlane = -1;
   }

   //////////////////////////////////////////////////////////////////////////
   // Processes buttons' and sliders' events.
   //////////////////////////////////////////////////////////////////////////
   public void InputCallback( GlgObject vp, GlgObject message_obj )
   {
      double scale, value, value_x, value_y, x, y;
      String
        format,
        action,
        origin;

      super.InputCallback( vp, message_obj );

      format = message_obj.GetSResource( "Format" );
      origin = message_obj.GetSResource( "Origin" );
      action = message_obj.GetSResource( "Action" );

      if( format.equals( "Slider" ) && action.equals( "ValueChanged" ) )
      {
         if( origin.equals( "Zoom" ) )   // Handle zooming
         {
            // Zoom into a map 
            value = GetDResource( message_obj, "ValueX" );
            SetDResource( map_viewport, "Scale", 1.2 / ( 0.2 + value ) );

            // Activate Pan control when zommed in.
            SetDResource( "Pan/Enabled", value < 0.99 ? 1. : 0. );
            Update();
         }
         else if( origin.equals( "Pan" ) )  // Handle panning
         {
            // Pan 
            scale = GetDResource( map_viewport, "Scale" );
            value_x = GetDResource( message_obj, "ValueX" );
            value_y = GetDResource( message_obj, "ValueY" );
            x = 1000. * ( -1 + 2. * value_x );
            y = 1000. * ( -1 + 2. * value_y );
            SetGResource( map_viewport, "PanCenter",
                         0.35 * x * ( 1. - scale ) ,
                         0.35 * y * ( 1. - scale ), 0. );
            Update();
         }
      }
      else if( format.equals( "Button" ) && action.equals( "Activate" ) )
      {
         if( origin.equals( "Restart" ) )
           Restart();
         else if( origin.equals( "Big Planes" ) )  // Increase plane size
           IncreaseSize();	    
         else if( origin.equals( "Small Planes" ) )  // Decrease plane size
           DecreaseSize();
         else if( origin.equals( "Delete Map" ) )
           SetMapVisibility( 0. );
         else if( origin.equals( "Add Map" ) )
           SetMapVisibility( 1. );
         else if( origin.equals( "Quit" ) )
           System.exit( 0 );
      }
   }

   //////////////////////////////////////////////////////////////////////////
   public void SelectCallback( GlgObject vp, Object[] name_array, int button )
   {
      int
        i,
        plane_number_start,
        plane_number_end,
        plane_number;
      String
        name,
        number_str;

      if( name_array == null )
        return;

      for( i=0; ( name = (String) name_array[i] ) != null; ++i )
      {
         if( SelectedPlane != -1 && name.equals( "Info" ) )
         {
            // Close the plane info display
            UnselectPlane();
            SetDResource( "Info/Visibility", 0. );
            SelectedPlane = -1;
            Update();	 
            break;
         }
         
         plane_number_start = name.indexOf( "/plane" );
         if( plane_number_start == -1 )
           continue;

         plane_number_start += "/plane".length();
         
         // Extract the plane number
         plane_number_end = name.indexOf( plane_number_start, '/' );
         if( plane_number_end == -1 )
           number_str = name.substring( plane_number_start );
         else
           number_str = name.substring( plane_number_start, plane_number_end );

         try
         {
            plane_number = new Integer( number_str ).intValue();
         }
         catch( NumberFormatException e )
         {
            continue;
         }

         SelectPlane( plane_number );

         Update();
         break;
      }
   }

   //////////////////////////////////////////////////////////////////////////
   boolean NoPlanesLeft()
   {
      int i;

      for( i=0; i<NumPlanes; ++i )
        if( !Planes[ i ].destroyed )
          return false;

      return true;
   }

   //////////////////////////////////////////////////////////////////////////
   void AdjustPlanesScale( double factor )
   {
      int i;
      double scale;

      for( i=0; i<NumPlanes; ++i )
        if( !Planes[ i ].destroyed )
        {
           scale = GetDResource( Planes[ i ].object, "scale" );
           SetDResource( Planes[ i ].object, "scale", scale * factor );
        }
      
      Update();
   }

   //////////////////////////////////////////////////////////////////////////
   public void Toggle3D()
   { 
      Use3D = !Use3D;
      Restart();
   }

   //////////////////////////////////////////////////////////////////////////
   public void Restart()
   { 
      for( int i=0; i<NumPlanes; ++i )
        if( !Planes[ i ].destroyed )
          DeletePlane( i, false );

      CreatePlanes( NumPlanes );
      Update();
   }

   //////////////////////////////////////////////////////////////////////////
   public void IncreaseSize()
   {
      PlaneScale *= 1.5;
      AdjustPlanesScale( 1.5 );
   }

   //////////////////////////////////////////////////////////////////////////
   public void DecreaseSize()
   {
      PlaneScale /= 1.5;
      AdjustPlanesScale( 1. / 1.5 );
   }

   //////////////////////////////////////////////////////////////////////////
   public void SetMapVisibility( double visibility )
   {
      SetDResource( map_viewport, "MapGroup/Visibility", visibility );
      Update();
   }

   //////////////////////////////////////////////////////////////////////////
   // ActionListener method to use the bean as update timer's ActionListener.
   //////////////////////////////////////////////////////////////////////////
   public void actionPerformed( ActionEvent e )
   {
      if( timer != null )
        UpdateBattleField();
   }
}


