import java.applet.*; import java.awt.*; 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. // These methods are compatible with the methods of the Glg Plug-in. // 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 { ////////////////////////////////////////////////////////////////////////// // 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 = 20., // 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 = 10, NumPlanePairs = 3, 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(); SetDResource( "$config/GlgSwingUsage", 1. ); } ////////////////////////////////////////////////////////////////////////// // Stops the update thread ////////////////////////////////////////////////////////////////////////// public void stop() { StopUpdate(); IsReady = false; super.stop(); } ////////////////////////////////////////////////////////////////////////// public void StartUpdate() { if( timer == null ) { timer = new Timer( 30, 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 using 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.show(); // 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 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 distance, dx, dy, dz, angle_y, angle_z; if( IS_PLANE( plane ) ) { 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= 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 ) { int i; double scale; if( plane == SelectedPlane ) UnselectPlane(); // In Swing, the drawing is not direct: it'll be flushed // on the screen only when finished: can't blink the object's // by repetitive drawing. So blink the planes only in AWT version. // 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