001         package com.croftsoft.apps.mars.view;
002    
003         import java.applet.*;
004         import java.awt.*;
005         import java.awt.geom.*;
006         import java.awt.image.*;
007         import java.io.*;
008         import java.net.*;
009         import javax.swing.*;
010    
011         import com.croftsoft.core.animation.ComponentAnimator;
012         import com.croftsoft.core.awt.image.ImageCache;
013         import com.croftsoft.core.lang.NullArgumentException;
014         import com.croftsoft.core.math.geom.Point2DD;
015         import com.croftsoft.core.math.geom.PointXY;
016         import com.croftsoft.core.math.geom.ShapeLib;
017         import com.croftsoft.core.media.sound.AudioClipCache;
018    
019         import com.croftsoft.apps.mars.model.TankAccessor;
020    
021         /*********************************************************************
022         * The tank view.
023         *
024         * @version
025         *   2003-07-17
026         * @since
027         *   2003-01-23
028         * @author
029         *   <a href="https://www.croftsoft.com/">David Wallace Croft</a>
030         *********************************************************************/
031    
032         public final class  TankAnimator
033           extends ModelAnimator
034         //////////////////////////////////////////////////////////////////////
035         //////////////////////////////////////////////////////////////////////
036         {
037    
038         private static final String  TANK_BODY_IMAGE_FILENAME
039           = "tank_body.png";
040    
041         private static final String  TANK_TREAD_IMAGE_FILENAME_PREFIX
042           = "tank_tread_";
043    
044         private static final String  TANK_TREAD_IMAGE_FILENAME_POSTFIX
045           = ".png";
046    
047         private static final String  BANG_AUDIO_FILENAME
048           = "bang.wav";
049    
050         private static final String  DRY_FIRE_AUDIO_FILENAME
051           = "dry_fire.wav";
052    
053         private static final int  TREAD_LENGTH = 5;
054    
055         private static final String  TANK_TURRET_IMAGE_FILENAME
056           = "tank_turret.png";
057    
058         private static final String [ ]  TANK_TREAD_IMAGE_FILENAMES
059           = createTankTreadImageFilenames ( );
060    
061         //
062    
063         private final AudioClipCache   audioClipCache;
064    
065         private final ImageCache       imageCache;
066    
067         private final AffineTransform  affineTransform;
068    
069         private final Point2DD         newCenter;
070    
071         private final Point2DD         oldCenter;
072    
073         //
074    
075         private final double  TANK_RADIUS;
076    
077         private final int     TANK_WIDTH;
078    
079         private final int     TANK_HEIGHT;
080    
081    //   private final int     REPAINT_SIZE;
082    
083         //
084    
085         private double   oldBodyHeading;
086    
087         private double   oldTurretHeading;
088    
089         private boolean  oldSparking;
090    
091         private int      treadOffsetLeft;
092    
093         private int      treadOffsetRight;
094    
095         private boolean  viewHasChanged;
096    
097         //////////////////////////////////////////////////////////////////////
098         // constructor methods
099         //////////////////////////////////////////////////////////////////////
100    
101         /*********************************************************************
102         * Main constructor.
103         *********************************************************************/
104         public  TankAnimator (
105           TankAccessor    tankAccessor,
106           ImageCache      imageCache,
107           AudioClipCache  audioClipCache )
108         //////////////////////////////////////////////////////////////////////
109         {
110           super ( tankAccessor );
111    
112           NullArgumentException.check ( this.imageCache = imageCache );
113    
114           NullArgumentException.check (
115             this.audioClipCache = audioClipCache );
116    
117           affineTransform = new AffineTransform ( );
118    
119           newCenter = new Point2DD ( );
120    
121           oldCenter = new Point2DD ( );
122    
123           TANK_RADIUS  = tankAccessor.getRadius ( );
124    
125           TANK_WIDTH   = 2 * ( int ) TANK_RADIUS;
126    
127           TANK_HEIGHT  = 2 * ( int ) TANK_RADIUS;
128    
129    //     REPAINT_SIZE = 2 * ( int ) TANK_RADIUS;
130         }
131    
132         //////////////////////////////////////////////////////////////////////
133         // interface ComponentAnimator methods
134         //////////////////////////////////////////////////////////////////////
135    
136         public void  update ( JComponent  component )
137         //////////////////////////////////////////////////////////////////////
138         {
139           super.update ( component );
140    
141           TankAccessor  tankAccessor = ( TankAccessor ) modelAccessor;
142    
143           if ( tankAccessor.isFiring ( ) )
144           {
145             audioClipCache.play ( BANG_AUDIO_FILENAME );
146           }
147           else if ( tankAccessor.isDryFiring ( ) )
148           {
149             audioClipCache.play ( DRY_FIRE_AUDIO_FILENAME );
150           }
151    
152           boolean  treadsMoving = true;
153    
154           ShapeLib.getCenter ( tankAccessor.getShape ( ), newCenter );
155    
156           double  bodyHeading = tankAccessor.getBodyHeading ( );
157    
158           if ( Math.abs ( oldBodyHeading - bodyHeading )
159             < 2.0 * Math.PI / 1000.0 )
160           {
161             if ( newCenter.distanceXY ( oldCenter ) < 0.25 )
162             {
163               treadsMoving = false;
164             }
165             else
166             {
167               treadOffsetLeft  = ( treadOffsetLeft  + 1 ) % TREAD_LENGTH;
168    
169               treadOffsetRight = ( treadOffsetRight + 1 ) % TREAD_LENGTH;
170             }
171           }
172           else
173           {
174    // use accessor state instead
175    
176             if ( turningRight ( oldBodyHeading, bodyHeading ) )
177             {
178               treadOffsetLeft  = ( treadOffsetLeft + 1 ) % TREAD_LENGTH;
179    
180               treadOffsetRight = ( treadOffsetRight + TREAD_LENGTH - 1 )
181                 % TREAD_LENGTH;
182             }
183             else
184             {
185               treadOffsetLeft = ( treadOffsetLeft + TREAD_LENGTH - 1 )
186                 % TREAD_LENGTH;
187     
188               treadOffsetRight  = ( treadOffsetRight  + 1 ) % TREAD_LENGTH;
189             }
190    
191             oldBodyHeading = bodyHeading;
192           }
193    
194           oldCenter.setXY ( newCenter );
195    
196           double  turretHeading = tankAccessor.getTurretHeading ( );
197    
198           boolean  sparking = tankAccessor.isSparking ( );
199    
200    // use the update flag instead
201    
202           viewHasChanged
203             = treadsMoving
204             || ( turretHeading != oldTurretHeading )
205             || ( sparking      != oldSparking      );
206    
207           oldTurretHeading = turretHeading;
208    
209           oldSparking      = sparking;
210         }
211    
212         protected void  getRepaintRectangle ( Rectangle  repaintRectangle )
213         //////////////////////////////////////////////////////////////////////
214         {
215           ShapeLib.getCenter ( modelAccessor.getShape ( ), newCenter );
216    
217           double  radius = 1.5 * TANK_RADIUS;
218    
219           int  diameter = ( int ) ( 2.0 * radius ) + 1;
220    
221           repaintRectangle.setBounds (
222             ( int ) ( newCenter.x - radius ) - 2,
223             ( int ) ( newCenter.y - radius ) - 2,
224             diameter + 1,
225             diameter + 1 );
226         }
227    
228         public void  paint (
229           JComponent  component,
230           Graphics2D  graphics )
231         //////////////////////////////////////////////////////////////////////
232         {
233           if ( !modelAccessor.isActive ( ) )
234           {
235             return;
236           }
237    
238           TankAccessor  tankAccessor = ( TankAccessor ) modelAccessor;
239    
240           ShapeLib.getCenter ( tankAccessor.getShape ( ), newCenter );
241    
242           double  x = newCenter.x;
243    
244           double  y = newCenter.y;
245    
246           double  adjustedHeading = tankAccessor.getBodyHeading ( );
247    
248           affineTransform.setToTranslation ( x, y );
249    
250           affineTransform.rotate ( adjustedHeading );
251    
252           affineTransform.translate (
253             -TANK_WIDTH / 2, -TANK_HEIGHT / 2 );
254    
255           try
256           {
257             for ( int  i = 0; i < TANK_WIDTH / TREAD_LENGTH; i++ )
258             {
259               Image  tankTreadImage = imageCache.get (
260                 TANK_TREAD_IMAGE_FILENAMES [ treadOffsetLeft ] );
261             
262               graphics.drawImage ( tankTreadImage, affineTransform, null );
263    
264               affineTransform.translate ( TREAD_LENGTH, 0 );
265             }
266    
267             affineTransform.setToTranslation ( x, y );
268    
269             affineTransform.rotate ( adjustedHeading );
270    
271             affineTransform.translate (
272               -TANK_WIDTH / 2, -TANK_HEIGHT / 2 + 40 );
273    
274             for ( int  i = 0; i < TANK_WIDTH / TREAD_LENGTH; i++ )
275             {
276               Image  tankTreadImage = imageCache.get (
277                 TANK_TREAD_IMAGE_FILENAMES [ treadOffsetRight ] );
278             
279               graphics.drawImage ( tankTreadImage, affineTransform, null );
280    
281               affineTransform.translate ( TREAD_LENGTH, 0 );
282             }
283    
284             affineTransform.setToTranslation ( x, y );
285    
286             affineTransform.rotate ( adjustedHeading );
287    
288             affineTransform.translate (
289               -TANK_WIDTH / 2, -TANK_HEIGHT / 2 + 10 );
290    
291             Image  tankBodyImage
292               = imageCache.get ( TANK_BODY_IMAGE_FILENAME );
293    
294             graphics.drawImage ( tankBodyImage, affineTransform, null );
295    
296             affineTransform.setToTranslation ( x, y );
297    
298             affineTransform.rotate ( tankAccessor.getTurretHeading ( ) );
299    
300             affineTransform.translate ( -TANK_WIDTH / 2, -TANK_HEIGHT / 2 );
301    
302             Image  tankTurretImage
303               = imageCache.get ( TANK_TURRET_IMAGE_FILENAME );
304    
305             graphics.drawImage ( tankTurretImage, affineTransform, null );
306           }
307           catch ( IOException  ex )
308           {
309             ex.printStackTrace ( );
310           }
311    
312           graphics.setColor ( tankAccessor.getColor ( ) );
313    
314           graphics.drawOval ( ( int ) x - 2, ( int ) y - 2, 4, 4 );
315    
316           if ( tankAccessor.isSparking ( ) )
317           {
318             graphics.setColor ( Color.RED );
319    
320             graphics.fillRect ( ( int ) x - 5, ( int ) y - 5, 10, 10 );
321           }
322         }
323    
324         //////////////////////////////////////////////////////////////////////
325         //////////////////////////////////////////////////////////////////////
326    
327         private static String [ ]  createTankTreadImageFilenames ( )
328         //////////////////////////////////////////////////////////////////////
329         {
330           String [ ]  tankTreadImageFilenames = new String [ TREAD_LENGTH ];
331    
332           for ( int  i = 0; i < TREAD_LENGTH; i++ )
333           {
334             tankTreadImageFilenames [ i ]
335               = TANK_TREAD_IMAGE_FILENAME_PREFIX
336               + i
337               + TANK_TREAD_IMAGE_FILENAME_POSTFIX;
338           }
339    
340           return tankTreadImageFilenames;
341         }
342    
343         private static boolean  turningRight (
344           double  oldHeading,
345           double  newHeading )
346         //////////////////////////////////////////////////////////////////////
347         {
348           if ( oldHeading > newHeading )
349           {
350             return oldHeading - newHeading > Math.PI;
351           }
352           else
353           {
354             return newHeading - oldHeading < Math.PI;
355           }
356         }
357    
358         //////////////////////////////////////////////////////////////////////
359         //////////////////////////////////////////////////////////////////////
360         }