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 }